sever端socket连接的流程:创建socket ====> 绑定ip和端口 ====> 监听listen ====> 接受连接accept
由于上述流程是通用的,过程中还有大量的错误判断和网络字节序的转换,因此封装成一些函数方便调用。
其中accpet易受信号中断和软件中断,使用goto语句,在中断时再次accept。
端口复用:
在server的TCP连接没有完全断开之前不允许重新监听是不合理的。因为,TCP连接没有完全断开指的是connfd(127.0.0.1:6666)没有完全断开,而我们重新监听的是lis-tenfd(0.0.0.0:6666),虽然是占用同一个端口,但IP地址不同,connfd对应的是与某个客户端通讯的一个具体的IP地址,而listenfd对应的是wildcard address。解决这个问题的方法是使用setsockopt()设置socket描述符的选项SO_REUSEADDR为1,表示允许创建端口号相同但IP地址不同的多个socket描述符。
在server代码的socket()和bind()调用之间插入如下代码:
int opt = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
pub.c文件
// pub.c
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<stdlib.h>
#include<string.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<errno.h>
// 创建socket的封装, 输入ip和port,成功返回lfd,失败返回 -1
int socketBind(char *ip, int port)
{
int lfd = 0;
//创建套接字
lfd = socket(AF_INET, SOCK_STREAM, 0);
if(-1 == lfd){
perror("socket create");
return -1;
}
int opt = 1;
if( setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1)
{
perror("setsockopt");
return -1;
}
//绑定
char buf[24] = "";
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
if(addr.sin_addr.s_addr = inet_ntop(AF_INET, ip, buf, sizeof(addr)) == NULL )
{
perror("inet_pton");
return -1;
}
int ret = bind(lfd, (struct sockaddr*)&addr, sizeof(addr));
if(-1 == ret)
{
perror("bind");
return -1;
}
//监听
ret = listen(lfd, 128);
if(-1 == ret)
{
perror("listen");
return -1;
}
return lfd;
}
// 封装Accept,返回新的socket连接cfd。避免在accept过程中被ECONNABORTED和EINTR信号打断导致错误。
int Accept(int lfd, struct sockaddr *sa, socklen_t *salenptr)
{
int n;
again:
if( (n = accept(lfd, sa, salenptr)) < 0 )
{
if( (errno == ECONNABORTED) || (errno == EINTR) ) // 如果被信号中断或者软件层次中断不能退出。
goto again;
else
perror("accept");
}
return n;
}
// 带打印客户端ip的accept
int Accept_with_print(int lfd)
{
int cfd = -1;
char ip[36] = "";
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
socklen_t len = sizeof(addr);
cfd = Accept(lfd, (struct sockaddr*)&addr, &len);
printf("client connected, ip=%s, port=%d\n",
inet_ntop(AF_INET, &addr.sin_addr.s_addr, ip, sizeof(ip)), ntohs(addr.sin_port));
return cfd;
}
pub.h文件
// pub.h
#ifndef _PUB_H_
#define _PUB_H_
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<stdlib.h>
#include<string.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<errno.h>
// 创建socket的封装, 输入ip和port,成功返回lfd,失败返回 -1
int socketBind(char *ip, int port);
// 封装Accept,返回新的socket连接cfd。避免在accept过程中被ECONNABORTED和EINTR信号打断导致错误。
int Accept(int lfd, struct sockaddr *sa, socklen_t *salenptr);
// 带打印客户端ip的accept
int Accept_with_print(int lfd);
#endif