网络编程详解-含一些开发总结

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/tyhaotingdege/article/details/84945091

        开始介绍前,说点经验之谈,希望能有所帮助,在项目开发中肯定涉及到多进程/线程,这时使用网络编程的系统调应十分小心,也就是在程序设计时应注意:

        (1)假如父进程服务端,子进程客户端(发个心跳什么的),若用子进程创建socket,然后利用该socket去发心跳,这样是行不通的,因为每个子进程socket产生的fd是相同的(多数人会认为创建出的fd不同),所以子进程中bind的是同一个fd,肯定error嘛。最好应该在父进程就建立好多条sokect,在子进程中去使用,提醒:合理使用容器很重要(vector+unordered_map)。

        (2)子进程除了发心跳,肯定要处理什么event吧,我们在接受事件(accept)时应该小心“惊群效应”,也就是在父进程listen,子进程accept时,会出现多个子进程同时去accept的现象,但我们只希望一个子进程执行accept,所以在应加锁,子进程accept和recv完后,再释放锁。

        (3)epoll_wait也有不少坑需要避让,详见:epoll详解(边沿触发/水平触发),小心epoll_wait

#include <sys/socket.h>
int socket(int domain, int type, int protocol);

       功能:实现同一主机 或 网络中的不同主机 进程间通信,进程通过返回值访问套接字。socket默认是阻塞,往往高并发服务器中需要通过fcntl函数重设置为非阻塞模式。

       返回值:成功,返回套接字的文件描述符。失败,返回 -1。

       参数 domain:域,表示通信特性,可选参数:

        (1)AF_INET:IPv4因特网域。

        (2)AF_INET6:IPv6因特网域。

        (3)AF_UNIX:以后讨论,别名:AF_LOCAL。

        (4)AF_UPSPEC:“任何”域。

       参数 type:类型,确定套接字类型,进一步确定通信特性。可选参数:

        (1)SOCK_DGRAM:固定长度、无连接、不可靠报文传输。无连接套接字发送的报文,每个报文都有目的地址。

        (2)SOCK_RAM:IP协议的数据报接口。

        (3)SOCK_SEQPACKET:固定长度、面向连接、有序、可靠报文传输。有连接方式发送报文,只连接建立好,在消息传递过程中,消息是不包含目的地址的。

        (4)SOCK_STREAM:有序、可靠、双向、面向连接的字节流。

       参数protocol:协议,通常是 0,表示为给定域和类型选择默认的通信协议。比如:IPPROTO_IP(ipv4 网际协议)、IPPROTO_IPV6(ipv6 网际协议)。

#include <sys/socket.h>
int shutdown(int sockfd, int how);

        功能:套接字是双向的,可以使用该函数来禁止一个套接字I/O。

        返回值:成功返回 0;失败返回 -1。

        参数how:SHUT_RD:关闭读端。SHUT_WR:关闭写端。SHUT_RDWR:关闭读写端。

        为什么有了close 还要shutdown 呢?因为close 的关闭只有使用这个套接字的所有引用都关闭才会将套接字关闭,而shutdown 允许套接字处于不活跃状态, 和引用这个套接字文件描述符的数目无关。

#include <sys/socket.h>
int bind(int sockfd, const stuct sockaddr *addr, socklen_ len);


struct sockaddr_in {  
    short            sin_family;       // 2 bytes e.g. AF_INET, AF_INET6  
    unsigned short   sin_port;    // 2 bytes e.g. htons(3490)  
    struct in_addr   sin_addr;     // 4 bytes see struct in_addr, below  
    char             sin_zero[8];     // 8 bytes zero this if you want to  
};  

struct in_addr {  
    unsigned long s_addr;          // 4 bytes load with inet_pton(),ip
};  

        功能:将一个客户端的套接字关联上一个地址。

        返回值:成功返回 0;失败返回 -1。

        参数:

        (1)sockfd:socket()函数返回的套接字文件木描述符。

        (2)sockaddr:sockaddr_in 和 sockaddr都是地址结构,都为16字节的结构体。内核不关心地址结构(所以之间可进行类型转换),当它复制或传递地址给驱动的时候,它依据 socklen_ 确定需要复制多少数据。

        (3)socklen_:即是sizeof(sockaddr)。

        这个绑定的地址有以下限制:

        (1)这个指定的地址在进程运行的计算机上必须是有效的,不能指定其它机器的地址。

        (2)地址必须和创建套接字时的地址族所支持的格式相匹配。

        (3)地址中的端口不小于1024。

        (4)一般只能将一个套接字端点绑定在一个给定地址上,尽管有些协议支持重绑定。

        可以调用getsockname 来发现绑定在套接字上的地址;用getpeername 来找到连接上的对方地址。

        

        由于网络侧采用大端模式进行数据的传输,可我们的主机不一定都采用大端方式进行数据存储,比如x86架构小端、c51架构大端(《can通信》里详细说明),所以给sockaddr_in 赋值时往往会用到这几个函数进行数据的转换:

    

(1)u_short htons( u_short hostshort ):host to net short,将端口号填入sockaddr_in.sin_port,会使用到该函数进行转换。

(2)uint32_t htonl(  uint32_t hostlong ):host to net long,将ip填入sockaddr_in.sin_addr.s_addr,会使用到该函数进行转换。

(3)char * inet_ntoa(struct  in_addr):常用于,accept收到的sockaddr_in.sin_addr转换为点分十进制。

(4) in_addr_t inet_addr(const char *):将点分十进制字符串,转为无符号长整型。使用该函数,也自动将数据转为了大端。

(5)in_port_t atoi(const char *):字符串转整形。
#include <sys/socket.h>
int connect(int sockfd, const stuct sockaddr *addr, socklen_ len);

        功能:建立连接。。

        返回值:成功返回 0;失败返回 -1。

        参数:指定地址是我们想通信的服务器地址。如果如果sockfd没有绑定地址,connect会给调用者绑定一个默认地址。

#include <sys/socket.h>
int listen(int sockfd, int backlog);

        功能:接受连接请求。listen只是将主动套接字接口(sockfd)变为被动套接字接口,设置内核接受此套接字连接请求。并不建立连接,三次握手发生在listen和accept之间

        返回值:成功返回 0;失败返回 -1。

        参数:backlog,表示已完成3次握手,但未accept的最多连接数,这个值的指定只是参考,实际上内核会做一些调整。一旦该队列满,系统便会拒绝多余连接请求。

        细节:对于上述的“三次握手连接是在listen和accept之间完成的”这句话进行解释,listen将“主动套接字接口”置为“被动套接字接口”后,该接口就可以称之为监听接口,内核为监听端口维护两个队列:(1)未完成连接的队列(服务器处于SYN_RECV状态)。(2)完成连接的队列。accept是从完成连接队列中取出最前边使用,并将其改一个新fd。

        3次握手的步骤:

        (1)客户端close,服务端listen。

        (2)客户端发送SYN,处于SYN_SEND状态。(一次)

        (3)服务端收到该信息,变为SYN_RECV状态,并回复客户端。(二次)

        (4)客户端收到消息,变为ESTABLISHED状态,并发ACK确认消息。(三次)

        (5)服务端收到到ACK,变为ESTABLISHED状态。

#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *len);

        功能:获得连接请求,返回新的套接字fd。

        返回值:成功,返回套接字的文件描述符。失败,返回 -1。

        参数:参数 sockfd:表示原始套接字文件描述符,与返回的文件描述符是不同的。参数 addr:用来存放客户端的地址信息,不使用置为NULL。参数 len:表示客户端地址的大小,不使用置为NULL

        细节:为什么accept要放在三次握手后呢?其实在以前accept是放在上述三次握手的第三步,accept会分配资源给连接,现假设在第三步后accept,此时连接还没有建立,要是第四步客户端不进行回复,那就是既没建立好连接却又分配了资源,这个漏洞导致了“DDOS攻击”的产生。

#include <sys/socket.h>
int send( SOCKET s, const char FAR *buf, int len, int flags );

        功能:从tcp连接到一段将消息发往另一端。

        返回值:成功返回字节数,失败返回 SOCKET_ERROR,即-1。

        参数:

        (1)第一个参数是需要使用的套接字连接符,accept后改变的那个。

        (2)要发送的数据存放位置。

        (3)数据大小,字节。

        (4)通常为0。

#include <sys/socket.h>
int recv( SOCKET s, char FAR *buf, int len, int flags);

        和 send 函数基本相同,只要注意与send函数的buf 的区别就好。

int setsockopt(int sock, int level, int optname, const void *optval, socklen_t optlen);

        功能:设置某level 的状态。

        返回值:成功0。失败-1,errno被设置。

        参数:细明参见:https://blog.csdn.net/xioahw/article/details/4056514

        这里说明一下level为:SOL_SOCKET ,optname为:SO_REUSEADDR,这时表示端口可重用,当optval非0时,重用bind中的地址。

        该函数有关心跳包的设置:https://blog.csdn.net/aa2650/article/details/17027845https://blog.csdn.net/ctthuangcheng/article/details/8596818

        网络编程注意点:

        (1) 多个子进程socket产生的socket_fd数值是一样的,但表示的是不同的套接字,因为fd的使用范围是进程,不同进程,相同的fd,也可能表示不同文件。

        (2) 正常情况下,多进程不能同时bind同一个ip:port,如果有这样的需求,可使用setsockopt设置SO_REUSEPORT。SO_REUSEPORT是避免“惊群”的最好方法了,暂时没有之一

        (3) 服务端多个进程使用SO_REUSEPORT,bind同一ip:port,accept时不会报错的。但是客户端多个进程使用SO_REUSEPORT,bind同一ip:port,在connect时还是会报错的。

猜你喜欢

转载自blog.csdn.net/tyhaotingdege/article/details/84945091
今日推荐