TCP套接字编程步骤
客户端
- 创建套接字
- 绑定地址信息(不推荐,因为系统会帮你绑定,这样能预防端口冲突)
- 向客户端发送连接请求
- 发送/接收数据
- 关闭套接字
服务端
- 创建套接字
- 绑定地址信息(客户端必须自己绑定,系统不会帮你)
- 将套接字设为监听状。此状态下一旦接收到连接请求,就会创建一个新的 socket 并放入未完成连接队列里面去,等到连接建立完成,就会将它再次转到已完成连接队列里去。(若未完成连接队列已满则新的连接请求会被丢弃,直到腾出空间才再次建立连接)
- 获取新连接。从已完成连接队列中取出一个套接字,并返回其操作句柄。(新 socket 中拥有源端的地址信息)
- 接收/发送数据
- 关闭套接字
注意点:
- 监听套接字只负责进行建立连接,而不负责进行通信
- 新建套接字负责进行通信
- 客户端需要使用多进程或者多线程,因为同一时间可能有多个客户端建立连接(建议使用多进程,因为稳定性和健壮性强)
- 多进程中父进程需要注意子进程的退出状态,避免出现僵尸进程
图示流程
函数接口介绍
1. 创建套接字
int socket(int domain, int type, int protocol);
参数:
domain: 地址域,不同的协议版本有着不同的地址结构
常用的有 AF_INET(ipv4) AF_INET6(ipv6)
type: 套接字类型 tcp(流式套接字) SOCK_STREAM udp(数据报套接字)SOCK_DGRAM
protocol: 传输协议, tcp(IPPROTO_TCP) udp(IPPROTO_UDP)
返回值: 成 套接字的操作句柄 败 -1
2. 为套接字绑定地址信息(绑定自己的)
客户端不推荐,因为系统会帮助绑定,这样可以避免端口冲突,服务端必须自己绑定
int bind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen);
参数:
sockfd: 套接字的操作句柄
my_addr: 要绑定的地址信息,这是一个结构体,需要用地址信息给结构体的每一个变量赋值(因为不同的地址结构开头都是一致的,为了避免出错,这里需要进行结构体强转)
addrlen: 要绑定的地址信息的长度,也就是my_addr这个结构体的长度
返回值: 成 0 败 -1
3. 客户端向服务端发起连接请求
int connect(int sockfd,const struct sockaddr *srv_addr,socklen_t addrlen);
参数:
sockfd 系统调用的套接字描述符,即由socket函数返回的套接字描述符
srv_addr 目的套接字的地址,该套接字地址结构必须包含目的IP地址和目的端口号,即想与之通信的服务器地址
addrlen 目的套接字地址的大小
返回值:若成功则为0,若出错则为-1
4. 客户端进入监听状态
int listen(int sockfd, int backlog);
参数:
sockfd 套接字的操作句柄
backlog 同一时间的并发连接数,决定同一时间最多接收多少个连接请求
返回值:若成功则返回0,若出错则返回-1;
5.获取新连接(从已完成队列中去一个socket,并返回其操作句柄)
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);
参数:
cliaddr 和 addrlen 用来返回已连接的对端(客户端)的协议地址;
该函数返回套接字描述符,该描述符连接到调用connect函数的客户端.
这个新的套接字描述符和原始的套接字描述符sockfd具有相同的套接字类型和地址族,而传给accept函数的套接字描述符sockfd没有关联到这个链接,而是继续保持可用状态并接受其他连接请求.若不关心客户端协议地址,可将cliaddr和addrlen参数设置为NULL,否则,在调用accept之前,应将参数cliaddr设为足够大的缓冲区来存放地址,并且将addrlen设为指向代表这个缓冲区大小的整数指针.accept函数返回时,会在缓冲区填充客户端的地址并更新addrlen所指向的整数为该地址的实际大小.若没有连接请求等待处理,accept会阻塞直到一个请求到来.
返回值:若成功返回套接字描述符,出错返回-1;
6.接收/发送数据
ssize_t recv(int sockfd, char *buf, size_t len, int flags);
ssize_t send(int sockfd, char *data, size_t len, int flags);
参数:
buf/data 是要接收/发送数据的首地址
flag 0 默认表阻塞
len 接收/发送数据的长度
返回值: 成 实际收/发的长度 0 表示一端断开连接 -1 失败
7.关闭套接字
int close(int sockfd)
代码演示