网络编程注意事项

创建进程时,必须要考虑以下两点:

1、父进程是否阻塞等待子进程结束;

2、子进程资源的释放

对于1是不考虑的,因为如果阻塞了,那么就不能同时处理其他客户端的连接了。那么不用wait等待子进程结束会有一个问题,就是子进程结束时会变成僵死进程(底部资源未被回收)。在这种情况下,可以在主进程注册SIG_CHLD信号,这个信号在子进程结束时发送,当主进程接收到这个信号时用wait回收结束了的子进程的资源。


创建线程时,必须要考虑以下两点:

1、主线程是否阻塞等待子线程结束(pthread_join);

2、子线程资源的释放

对于1是不考虑的,因为如果阻塞了,那么就不能同时处理其他客户端的连接了。那么不用join等待子线程结束会有一个问题,就是子线程结束时其底部资源未被回收。在这种情况下,一种是调用 pthread_detach() 函数,可以将线程 th 设置为 detached 状态;另一种方法是在创建线程时就将它设置为 detached 状态:

#include <pthread.h>

void pthread_exit(void *retval);

void pthread_join(pthread_t th,void *thread_return);//挂起等待th结束,*thread_return=retval;

int pthread_detach(pthread_t th);


创建 detach 线程:

pthread_t tid;

pthread_attr_t attr;

pthread_attr_init(&attr);

pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

pthread_create(&tid, &attr, THREAD_FUNCTION, arg);


通信(读写)注意事项:

对于tcp连接,对端关闭,读对端时返回0;对于udp链接,不需要判断对端是否关闭,因为不需要建立连接。也就是对于tcp,read数据时要判断小于0(读出错),等于0(对端关闭)以及大于0(正常),而对于udp,用recvfrom接受数据时,只需要判断小于0(接受数据出错)。

对端关闭读,则write到对端时返回值小于0且errno被设置为EPIPE。

对于udp,当调用接收数据的函数如recvfrom时,有可能由于对端发送的包丢失而一直阻塞,所以调用这些阻塞型的api时,可以加入超时设置。


绑定(bind)同一个端口和地址注意事项:

参考Linux下端口复用(SO_REUSEADDR与SO_REUSEPORT)

freebsd与linux下bind系统调用小结:

    只考虑AF_INET的情况(同一端口指ip地址与端口号都相同)
  • freebsd支持SO_REUSEPORT和SO_REUSEADDR选项,而linux只支持SO_REUSEADDR选项。
  • freebsd下,使用SO_REUSEPORT选项,两个tcp的socket可以绑定同一个端口;同样,使用SO_REUSEPORT选项,两个udp的socket可以绑定同一个端口。
  • linux下,两个tcp的socket不能绑定同一个端口;而如果使用SO_REUSEADDR选项,两个udp的socket可以绑定同一个端口。
  • freebsd下,两个tcp的socket绑定同一端口,只有第一个socket获得数据。
  • freebsd下,两个udp的socket绑定同一端口,如果数据包的目的地址是单播地址,则只有第一个socket获得数据,而如果数据包的目的地址是多播地址,则两个socket同时获得相同的数据。
  • linux下,两个udp的socket绑定同一端口,如果数据包的目的地址是单播地址,则只有最后一个socket获得数据,而如果数据包的目的地址是多播地址,则两个socket同时获得相同的数据。

    Unix网络API

SO_REUSEADDR和SO_REUSEPORT

SO_REUSEADDR提供如下四个功能:

  • SO_REUSEADDR允许启动一个监听服务器并捆绑其众所周知端口,即使以前建立的将此端口用做他们的本地端口的连接仍存在。这通常是重启监听服务器时出现,若不设置此选项,则bind时将出错。 
  • SO_REUSEADDR允许在同一端口上启动同一服务器的多个实例(多个进程),只要每个实例捆绑一个不同的本地IP地址即可。在有多块网卡或者用IP Alias技术的机器可以测试这种情况。 对于TCP,我们根本不可能启动捆绑相同IP地址和相同端口号的多个服务器。 
  • SO_REUSEADDR允许单个进程捆绑同一端口到多个套接口上,只要每个捆绑指定不同的本地IP地址即可。这一般不用于TCP服务器。 
  • SO_REUSEADDR允许完全重复的捆绑:当一个IP地址和端口绑定到某个套接口上时,还允许此IP地址和端口捆绑到另一个套接口上。一般来说,这个特性仅在支持多播的系统上才有,而且只对UDP套接口而言,不用于TCP(TCP不支持多播)。 

SO_REUSEPORT选项有如下语义:

  • 此选项允许完全重复捆绑,但仅在想捆绑相同IP地址和端口的套接口都指定了此套接口选项才行。 如果被捆绑的IP地址是一个多播地址,则SO_REUSEADDR和SO_REUSEPORT等效。 

使用这两个套接口选项的建议

  • 在所有TCP服务器中,在调用bind之前设置SO_REUSEADDR套接口选项。
  • 当编写一个同一时刻在同一主机上可运行多次的多播应用程序时,设置SO_REUSEADDR选项,并将本组的多播地址作为本地IP地址捆绑。


    Q:编写 TCP/SOCK_STREAM 服务程序时,SO_REUSEADDR到底什么意思?

    A:这个套接字选项通知内核,如果端口忙,但TCP状态位于 TIME_WAIT ,可以重用端口。如果端口忙,而TCP状态位于其他状态,重用端口时依旧得到一个错误信息,指明"地址已经使用中"。如果你的服务程序停止后想立即重启,而新套接字依旧使用同一端口,此时SO_REUSEADDR 选项非常有用。必须意识到,此时任何非期望数据到达,都可能导致服务程序反应混乱,不过这只是一种可能,事实上很不可能。

    一个套接字由相关五元组构成,协议、本地地址、本地端口、远程地址、远程端口。SO_REUSEADDR 仅仅表示可以重用本地本地地址、本地端口,整个相关五元组还是唯一确定的。所以,重启后的服务程序有可能收到非期望数据。必须慎重使用 SO_REUSEADDR 选项。



[cpp] view plain copy
 print?
  1. if( getsockopt( sockfd , SOL_SOCKET, SO_REUSEPORT,  
  2.                  ( char *)&optval, &optlen ) < 0 )  
  3.             printf( " get socket error /n" );  
  4.   
  5.   if( setsockopt( sockfd , SOL_SOCKET, SO_REUSEPORT,  
  6.          ( char *)&optval, sizeof( optval ) ) < 0 )  
  7.             printf( " set socket error /n" );  
  8.   
  9. 编译报错:  
  10. libtcp.c:1195: error: `SO_REUSEPORT' undeclared (first use in this function)  
  11. libtcp.c:1195: error: (Each undeclared identifier is reported only once  
  12. libtcp.c:1195: error: for each function it appears in.)  

需要改
/usr/include/asm/socket.h:/* To add :#define SO_REUSEPORT 15 */



What is the difference between SO_REUSEADDR and SO_REUSEPORT?
from:UNIX Socket FAQ 

SO_REUSEADDR allows your server to bind to an address which is in a TIME_WAIT state. It does not allow more than one server to bind to the same address. It was mentioned that use of this flag can create a security risk because another server can bind to a the same port, by binding to a specific address as opposed to INADDR_ANY. The SO_REUSEPORT flag allows multiple processes to bind to the same address provided all of them use the SO_REUSEPORT option. 

From Richard Stevens ([email protected]):

This is a newer flag that appeared in the 4.4BSD multicasting code (although that code was from elsewhere, so I am not sure just who invented the new SO_REUSEPORT flag).

What this flag lets you do is rebind a port that is already in use, but only if all users of the port specify the flag. I believe the intent is for multicasting apps, since if you're running the same app on a host, all need to bind the same port. But the flag may have other uses. For example the following is from a post in February:

From Stu Friedberg ([email protected]):

SO_REUSEPORT is also useful for eliminating the try-10-times-to-bind hack in ftpd's data connection setup routine. Without SO_REUSEPORT, only one ftpd thread can bind to TCP (lhost, lport, INADDR_ANY, 0) in preparation for connecting back to the client. Under conditions of heavy load, there are more threads colliding here than the try-10-times hack can accomodate. With SO_REUSEPORT, things work nicely and the hack becomes unnecessary. 

I have also heard that DEC OSF supports the flag. Also note that under 4.4BSD, if you are binding a multicast address, then SO_REUSEADDR is condisered the same as SO_REUSEPORT (p. 731 of "TCP/IP Illustrated, Volume 2"). I think under Solaris you just replace SO_REUSEPORT with SO_REUSEADDR.

From a later Stevens posting, with minor editing:

Basically SO_REUSEPORT is a BSD'ism that arose when multicasting was added, even thought it was not used in the original Steve Deering code. I believe some BSD-derived systems may also include it (OSF, now Digital Unix, perhaps?). SO_REUSEPORT lets you bind the same address *and* port, but only if all the binders have specified it. But when binding a multicast address (its main use), SO_REUSEADDR is considered identical to SO_REUSEPORT (p. 731, "TCP/IP Illustrated, Volume 2"). So for portability of multicasting applications I always use SO_REUSEADDR.


tcp:不能对同一个端口及地址绑定两次,如果启动两次绑定了同一个ip和port的服务器进程,第二次启动那个会提示错误(bind error:Address already in use);

udp:不能对同一个端口及地址绑定两次,如果启动两次绑定了同一个ip和port的服务器进程,第二次启动那个会提示错误(bind error:Address already in use);当通过setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt))设置重用地址和端口时,可以同时对同一个地址和端口重复绑定,当然,以第二次绑定为准,也就是,只有最后一次绑定的服务器程序能接收到客户端发送的报文。(当两个socket的address和port相冲突,而你又想重用地址和端口,则旧的socket和新的socket都要已经被设置了SO_REUSEADDR特性,只有两者之一有这个特性还是有问题的。 


并发处理注意事项:

多进程:每个进程服务一个客户端。优势是有各自独立的地址空间,可靠性高,但进程调度开销大,无法资源共享,进程间通信机制复杂。
海姹网(网址:http://www.seacha.com),标签:网络编程中并发服务器的设计模式, 服务器,网络,并发
多线程:每个线程服务一个客户端。优势是开销小,通信机制简单,可共享内存。但共享地址空间,可靠性低,一个服务器出现问题时可能导致系统崩溃,同时全局共享可能带来竞争,共享资源需要互斥,对编程要求高。如果大量创建撤销线程,也会浪费内存和cpu资源,可以考虑线程池。
线程池参考:linuxc简单线程池


你可能感兴趣的:(网络编程注意事项)