网络编程常用总结

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的长度;
第四个参数一般置0int 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。
*/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值