以下内容主要参考书籍《Linux C编程一站式学习》、《Unix网络编程》、《Unix高级环境编程》
首先要明确客户端与服务器要怎么去实现通信
下图便是一个简易的TCP C/S模型实现
知道模型之后,接下来只是一些与网络接口相关的API调用。
预备知识:
socket是什么?
1.在TCP 在TCP/IP协议中,“IP地址+TCP或UDP端口号”唯一标识网络通讯中的一个进程,“IP地址+端口号”就称为socket。
2.在TCP协议中,建立连接的两个进程各自有一个socket来标识,那么这两个socket组成的socket pair就唯一标识一个连接。socket本身有“插座”的意思,因此用来描述网络连接的一对一关系。
3.TCP/IP协议最早在BSD UNIX上实现,为TCP/IP协议设计的应用层编程接口称为socket接口
TCP4层模型与socket接口之间关系如下图
具体相关的函数接口
1.socket()
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain,int type,int protocol);
各参数解释如下
domain:
AF_INET 这是大多数用来产生socket的协议,使用TCP或UDP来传输,用IPv4的地址
AF_INET6 与上面类似,不过是来用IPv6的地址
AF_UNIX 本地协议,使用在Unix和Linux系统上,一般都是当客户端和服务器在同一台及其上的时候使用
type:
SOCK_STREAM 这个协议是按照顺序的、可靠的、数据完整的基于字节流的连接。当将protocol设为0时,则默认使用TCP来进行传输。
SOCK_DGRAM 这个协议是无连接的、固定长度的传输调用。该协议是不可靠的,当将protocol设为0时,则默认使用UDP来进行传输。
SOCK_SEQPACKET 这个协议是双线路的、可靠的连接,发送固定长度的数据包进行传输。必须把这个包完整的接受才能进行读取。
SOCK_RAW 这个socket类型提供单一的网络访问,这个socket类型使用ICMP公共协议。(ping、traceroute使用该协议)
SOCK_RDM 这个类型是很少使用的,在大部分的操作系统上没有实现,它是提供给数据链路层使用,不保证数据包的顺序
protocol:
0 默认协议
返回值:
成功返回一个新的文件描述符
失败返回-1,并设置errno
当进程调用socket函数后,系统会为其分配一个文件描述符,供进程去在网络上进行通信,对于文件描述符,我们就有许多对应的系统调用可以使用了,比如read,write等等。
但对于服务器来说,需要将本地地址绑定一个固定端口及相关的接口,来让客户端能够方便的访问到服务器,这时就需要一个函数接口bind()。
2.bind()
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockfd:
socket文件描述符
addr:
构造出IP地址加端口号
addrlen:
sizeof(addr)长度
这个函数重点在于struct sockaddr这个结构体,由于历史原因,当前并没有void *这个向上兼容的泛型指针,而所有的这些函数参数都使用struct sockaddr *类型表示,在使用的时候需要我们强转一下。
struct sockaddr {
sa_family_t sa_family; /* address family, AF_xxx */
char sa_data[14]; /* 14 bytes of protocol address */
};
IPV4地址使用struct sockaddr_in
struct sockaddr_in {
__kernel_sa_family_t sin_family; /* Address family */ IPV4此值取值为AF_INET
__be16 sin_port; /* Port number */
struct in_addr sin_addr; /* Internet address */
/* Pad to size of `struct sockaddr'. */
unsigned char __pad[__SOCK_SIZE__ - sizeof(short int) -
sizeof(unsigned short int) - sizeof(struct in_addr)];
};
/* Internet address. */
struct in_addr {
__be32 s_addr;
};
IPV6使用struct sockaddr_in6
struct sockaddr_in6 {
unsigned short int sin6_family; /* AF_INET6 */
__be16 sin6_port; /* Transport layer port # */
__be32 sin6_flowinfo; /* IPv6 flow information */
struct in6_addr sin6_addr; /* IPv6 address */
__u32 sin6_scope_id; /* scope id (new in RFC2553) */
};
struct in6_addr {
union {
__u8 u6_addr8[16];
__be16 u6_addr16[8];
__be32 u6_addr32[4];
} in6_u;
#define s6_addr in6_u.u6_addr8
#define s6_addr16 in6_u.u6_addr16
#define s6_addr32 in6_u.u6_addr32
};
本地套接字使用struct sockaddr_un
#define UNIX_PATH_MAX 108
struct sockaddr_un {
__kernel_sa_family_t sun_family; /* AF_UNIX */
char sun_path[UNIX_PATH_MAX]; /* pathname */
};
返回值:
成功返回0,失败返回-1, 并设置errno
当服务器将本地地址和一套接口绑定完之后,则需要给服务器设置为监听状态,使其有接受连接请求的能力,此时就需要函数接口listen()。
3.listen()
#include <sys/types.h> /* See NOTES */
#include <sys/s