TPC UDP SOCKET IO复用 编程知识
1.socket
int port = atoi(argv[1]);
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd<0)
return -1;
struct sockaddr_in addr;
memset(&addr, 0, sizeof(struct sockaddr_in));
addr.sin_family = AF_INET;// IP地址家族 TCP/IP – IPv4
addr.sin_port = htons(port);//必须转网络字节序
printf("addr.sin_port:%d\n",addr.sin_port);
//INADDR_ANY转换过来就是0.0.0.0,泛指本机的意思,也就是表示本机的所有IP,因为有些机子不止一块网卡,
//多网卡的情况下,这个就表示所有网卡ip地址的意思。
addr.sin_addr.s_addr = INADDR_ANY;
//addr.sin_addr.s_addr = inet_addr("127.0.0.1");
/*
struct sockaddr {
sa_family_t sin_family;//地址族
char sa_data[14]; //14字节,包含套接字中的端口和目标地址信息
};
*/
if(bind(sockfd,(struct sockaddr*)&addr,sizeof(struct sockaddr_in)) <0 ){
return -2;
}
//第二个参数是未完成队列的大小 如果开启了syncookies 忽略listen的第二个参数
//默认已经开启 可以这样开启echo 1 > /proc/sys/net/ipv4/tcp_syncookies
/*
SYN Cookie是对TCP服务器端的三次握手做一些修改,专门用来防范SYN Flood攻击的一种手段。它的原理是,在TCP服务器
接收到TCP SYN包并返回TCP SYN + ACK包时,不分配一个专门的数据区,而是根据这个SYN包计算出一个cookie值。这个
cookie作为将要返回的SYN ACK包的初始序列号。当客户端返回一个ACK包时,根据包头信息计算cookie,与返回的确认序列
号(初始序列号 + 1)进行对比,如果相同,则是一个正常连接,然后,分配资源,建立连接
*/
/*
在linux 2.2以后 listen的第二个参数。指的是在完成TCP三次握手后的队列。即在系统accept之前的队列。
已经完成的队列。如果系统没有调用accpet把这个队列的数据拿出来。一旦这个队列满了。未连接队列的请求过不来。
导致未连接队列里的请求会超时或者拒绝。
如果系统调用了accpet队列接受请求数据。那么就会把接受到请求移除已完成队列。 这时候已完成队列又可以使用了
*/
if(listen(sockfd,5) < 0){
return -3;
}
//关于socket属性设置
//1.读超时
bool SetRecvTimeOut(uint32 millisecond)
{
#ifdef WIN32
DWORD time = millisecond;
if (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char *)&time, sizeof(DWORD)) == -1)
{
return false;
}
#else struct
timeval tv;
tv.tv_sec = millisecond / 1000;
tv.tv_usec = (millisecond % 1000) * 1000;
if (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof(tv)) == -1)
{
return false;
}
#endif
return true;
}
//2 写超时 注意,linux和windows的参数略不同。
bool SetSendTimeOut(uint32 millisecond)
{
#ifdef WIN32
DWORD time = millisecond;
if (setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (const char *)&time, sizeof(DWORD)) == -1)
{
return false;
}
#else
struct timeval tv; tv.tv_sec = millisecond / 1000;
tv.tv_usec = (millisecond % 1000) * 1000;
if (setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (const char *)&tv, sizeof(tv)) == -1)
{
return false;
}
#endif
return true;
}
//3.一个端口释放后会等待两分钟之后才能再被使用,SO_REUSEADDR是让端口释放后立即就可以被再次使用。
bool SetReUseAddr(int v) {
int ret = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&v, sizeof(v)); return 0 == ret;
}
bool
//4.keepalive设置心跳包,还可以指定心跳包频率,不过建议还是在逻辑层设计心跳协议,来检查连接存活。
int val = 1;
setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val))
//5.TCP_NODELAY
bool SetNoDelay()
{
int yes = 1;
if (setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) == -1) { return false; } return true;
}
//6 设置阻塞0或非阻塞1
int SetNonBlock(int value)
{
#ifndef WIN32
int oldflags = ::fcntl(sock, F_GETFL, 0); /* If reading the flags failed, return error indication now. */
if (oldflags == -1)
return -1; /* Set just the flag we want to set. */
if (value != 0)
oldflags |= O_NONBLOCK;
else
oldflags &= ~O_NONBLOCK; /* Store modified flag word in the descriptor. */
return ::fcntl(m_iSock, F_SETFL, oldflags);
#else
if (::ioctlsocket(sock, FIONBIO, (u_long FAR *)&value) == SOCKET_ERROR)
{
return -1;
}
return 0;
#endif
}
2.recv 和 recvfrom
int recv( _In_ SOCKET s, _Out_ char *buf, _In_ int len, _In_ int flags);
第一个参数指定接收端套接字描述符;
第二个参数指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据;
第三个参数指明buf的长度;
第四个参数一般置0。
int PASCAL FAR recvfrom( SOCKET s, char FAR* buf, int len, int flags,struct sockaddr FAR* from, int FAR* fromlen);
buf:接收数据缓冲区。
len:缓冲区长度。
flags:调用操作方式。
from:(可选)指针,指向装有源地址的缓冲区。
fromlen:(可选)指针,指向from缓冲区长度值。
1.返回值=0 当返回值为0时,表示对端已经关闭了这个链接,我们应该自己关闭这个链接,即close(sockfd)。
另外因为异步操作会用select或epoll做事件触发,所以:
如果使用select,应该使用FD_CLR(sockfd,fd_set)将sockfd清除掉,不再监听。
如果使用epoll,系统会自己将sockfd清除掉,不再进行监听。
2.返回值>0 当返回值大于0 且 小于sizeof(buffer)时,表示数据肯定读完。
(如果等于sizeof(buffer),可能有数据还没读,应该继续读,不可能有大于)
3.返回值<0 当返回值小于0,即等于-1时,分情况判断:
1、如果errno为EAGAINE或EWOULDBLOCK
表示暂时无数据可读,可以继续读,或者等待epoll或select的后续通知。(EAGAINE,EWOULDBLOCK产生的原因:
可能是多进程读同一个sockfd,可能一个进程读到数据,其他进程就读取不到数据(类似惊群效应),当然
单个进程也可能出现这种情况。对于这种错误,不需用close(sockfd)。可以等待select或epoll的下一次触发再继续读。
3.send和sendto
int send( SOCKET s, const char FAR *buf, int len, int flags );
不论是客户还是服务器应用程序都用send函数来向TCP连接的另一端发送数据。
客户程序一般用send函数向服务器发送请求,而服务器则通常用send函数来向客户程序发送应答。
(1)第一个参数指定发送端套接字描述符;
(2)第二个参数指明一个存放应用程序要发送数据的缓冲区;
(3)第三个参数指明实际要发送的数据的字节数;
(4)第四个参数一般置0
返回值是实际发送的字符数,因为我们知道要发送的总长度,所以,如果没有发送完,我们可以继续发送。
1.返回值<0 当返回值为 -1 时,我们需要判断返回值errno:
如果errno为 EAGAINE或EWOULDBLOCK,表示当前缓冲区写满,可以继续写,
或者等待epoll或select的后续通知,一旦有缓冲区,就会触发写操作,这个也是经常利用的一个特性。
如果errno为EINTR ,表示被中断了,可以继续写,或者等待epoll或select的后续通知。
否则真的出错了,即errno不为EAGAINE或EWOULDBLOCK或EINTR,此时应该close(sockfd)
2.返回值>=0 且不等于要求发送的长度,应该继续send,如果等于要求发送的长度,发送完毕。
4.epoll
epoll_create(1);
//epoll_create()创建一个epoll实例。其中epfd为epoll句柄
//函数生成一个epoll专用的文件描述符。它其实是在内核申请一空间,用来存放你想关注的socket
//fd上是否发生以及发生了什么事件。size就是你在这个epoll fd上能关注的最大socket fd数。
//参数max_size标识这个监听的数目最大有多大,从Linux 2.6.8开始,max_size参数将被忽略,但必须大于零
//https://zhuanlan.zhihu.com/p/159135478
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
(1)第一个参数epfd 全局的epoll_creat()生成的fd
(2)第二个参数op
#define EPOLL_CTL_ADD 1 /* 在文件描述符epfd所引用的epoll实例上注册目标文件描述符fd,并将事件与内部文件链接到fd */
#define EPOLL_CTL_DEL 2 /* 从epfd引用的epoll实例中删除(注销)目标文件描述符fd。该事件将被忽略,并且可以为NULL */
#define EPOLL_CTL_MOD 3 /* 更改与目标文件描述符fd相关联的事件事件 */
(3)第三个参数fd 对应目标文件描述符fd socket()或者accept()函数生成的
(4)第四个参数event
struct epoll_event {
uint32_t events;/ * Epoll事件* /
epoll_data_t data;/ *用户数据变量* /
};
events可以是以下几个宏的集合:
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
typedef union epoll_data
{
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
一般用ptr指向要绑定的数据,一般是个结构体也可以是类,枚举公用内存只能用其中一个变量
epoll_ctl返回值:成功时,epoll_ctl()返回零。发生错误时,epoll_ctl()返回-1并正确设置了errno
/*
错误errno:
EBADF:epfd或fd不是有效的文件描述符。
EEXIST:op为EPOLL_CTL_ADD,并且提供的文件描述符fd已在该epoll实例中注册。
EINVAL:epfd不是epoll文件描述符,或者fd与epfd相同,或者此接口不支持请求的操作op。
ENOENT:op是EPOLL_CTL_MOD或EPOLL_CTL_DEL,并且fd未在该epoll实例中注册。
ENOMEM:没有足够的内存来处理请求的操作控制操作。
ENOSPC:尝试在主机上注册(EPOLL_CTL_ADD)新文件描述符时遇到了/ proc / sys / fs / epoll / max_user_watches施加的限制。
EPERM:目标文件fd不支持epoll。
*/