创建socket:
socket(int domain, int type, int protocol);
-
domain:即协议域,又称为协议族(family)。常用的协议族有,AF_INET(IPv4)、AF_INET6(IPv6)、AF_LOCAL(或称AF_UNIX,Unix域socket)、AF_ROUTE等等。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。
-
type:指定socket类型。常用的socket类型有,SOCK_STREAM(流式套接字)、SOCK_DGRAM(数据报式套接字)、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等
-
protocol:就是指定协议。常用的协议有,IPPROTO_TCP、PPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议。
SOCK_STREAM式套接字的通信双方均需要具有地址,其中服务器端的地址需要明确指定,ipv4的指定方法是使用 struct sockaddr_in类型的变量。
INADDR_ANY就是指定地址为0.0.0.0的地址,这个地址事实上表示不确定地址,或“所有地址”、“任意地址”。也就是表示本机的所有IP,因为有些机子不止一块网卡,多网卡的情况下,这个就表示所有网卡ip地址的意思。
客户端connect时,不能使用INADDR_ANY选项。必须指明要连接哪个服务器IP。
bind()绑定地址:
bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数解释:
int sockfd
:这是一个文件描述符,它代表了已经创建好的套接字。在Unix-like系统中,几乎所有的东西都是文件,包括网络套接字,因此它们都有一个文件描述符。const struct sockaddr *addr
:这是一个指向sockaddr
结构的指针,该结构包含了套接字要绑定的地址信息。sockaddr
结构是一个通用的地址结构,它用于在套接字函数中传递地址信息。对于Internet域(IPv4和IPv6),通常会使用sockaddr_in
或sockaddr_in6
结构,并在需要时将其强制转换为sockaddr
类型。socklen_t addrlen
:这是一个整数类型,它指定了addr
结构的大小(以字节为单位)。这个参数对于验证传递给函数的地址结构是否与调用者期望的地址结构相匹配非常重要。对于sockaddr_in
结构,addrlen
通常设置为sizeof(sockaddr_in)
。
操作步骤:
- 查找为进程文件描述符保存的底层套接字;
- 将内存从用户空间复制到内核空间;然后
- 让低层的套接字协议族来处理绑定操作。
listen()监听:
listen(int sockfd, int backlog);
-
第二个参数为相应socket可以排队的最大连接个数 即通过syn来设置半连接队列的长度。
-
socket()函数创建的socket默认是一个主动类型的,listen函数将socket变为被动类型的,等待客户的连接请求。
需要注意的是,backlog
并不是指服务器可以同时处理的连接数,而是指在服务器调用accept
之前,内核为该套接字排队的最大已完成(Established)连接数。
一旦我们为套接字设置了地址,接下来就是让它扮演服务器或客户端的角色。
也就是说,它需要将自己设置为侦听传入的连接,或者启动与正在侦听的其他人的连接。
在这里,我们通过listen()
来扮演服务器角色,即让自己来监听客户端连接。
connect()连接:
connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
- 第一个参数即为客户端的socket描述字,
- 第二参数为服务器的socket地址,
- 第三个参数为socket地址的长度。
connect()
函数是用于客户端发起连接的。
accept()接受client连接:
accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
- 第一个参数为服务器的socket描述字,
- 第二个参数为指向struct sockaddr *的指针,用于返回客户端的协议地址,
- 第三个参数为客户端协议地址的长度。如果accpet成功,那么其返回值是由内核自动生成的一个全新的描述字,代表与返回客户的TCP连接。
注意:
accept的第一个参数为服务器的socket描述字,是服务器开始调用socket()函数生成的,称为监听socket描述字;
而accept函数返回的是已连接的socket描述字。两个套接字不一样。
一个服务器通常通常仅仅只创建一个监听socket描述字,它在该服务器的生命周期内一直存在。内核为每个由服务器进程接受的客户连接创建了一个已连接socket描述字,当服务器完成了对某个客户的服务,相应的已连接socket描述字就被关闭。
至此服务器与客户已经建立好连接了。可以调用网络I/O进行读写操作了
recv()、send()、read()、write()等函数:
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);