socket:
TCP/IP协议中一个端口号和一个IP地址绑定在一起就生成一个socket就表示了网络中唯一的一个进程,它是全双工的工作方式。
基于TCP的socket编程
函数的使用:
1、socket()
#include/* See NOTES */ #include int socket(int domain, int type, int protocol);
socket它用于创建一个套接字,返回值是一个文件描述符。
domin表示该套接字网络层所用的协议,因为我们用的是iPv4协议,所以这里填AF_INET。
type表示该套接字传输层使用什么模式传输,TCP是字节流的,所以这里填SOCK_STREAM。
protocol填写为0,系统会根据你之前的参数把它设置为相应字段,比如我们参数设置为AF_INET,SOCK_STREAM,0,后面的0会自动设置为IPPROTO_TCP。
2、bind()
#include/* See NOTES */ #include int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockfd就是套接字
struct sockaddr:首先需要认识下面两个结构体struct sockaddr_in 和struct sockaddr_un。
通常情况下习惯于用sockaddr_in结构体来填充ip和端口号字段然后强转为struct sockaddr。
struct sockaddr_in { short sin_family;/*Address family一般来说AF_INET(地址族)PF_INET(协议族)*/ unsigned short sin_port;/*Port number(必须要采用网络数据格式,普通数字可以用htons()函数转换成网络数据格式的数字)*/ struct in_addr sin_addr; 使用inet_addr(),将字符串ip转化为网络数据的格式。 unsigned char sin_zero[8];/*Same size as struct sockaddr没有实际意义,只是为了 跟SOCKADDR结构在内存中对齐*/ };
3、网络字节序:
一台主机有大小端模式而在网络中传输数据的时候需要遵循一定的规则,即发送端从地地址开始发送,接收端从低地址开始接受,如果两台主机的大小端模式不一样,那么就会导致数据的错乱,tcp/ip协议中规定网络数据流应该采用大段的字节序,即高地址高位。为了方便就有了一系列转化的函数方便使用。
#includeuint32_t htonl(uint32_t hostlong); uint16_t htons(uint16_t hostshort); uint32_t ntohl(uint32_t netlong); uint16_t ntohs(uint16_t netshort); //上面这些函数用于将端口号转化为大端模式 //下面的多用于将ip地址字符串转化为大端模式 #include #include #include int inet_aton(const char *cp, struct in_addr *inp); in_addr_t inet_addr(const char *cp); in_addr_t inet_network(const char *cp); char *inet_ntoa(struct in_addr in); struct in_addr inet_makeaddr(int net, int host); in_addr_t inet_lnaof(struct in_addr in); in_addr_t inet_netof(struct in_addr in);
4、listen()
#include/* See NOTES */ #include int listen(int sockfd, int backlog);
listen的作用主要是用来将当前已经绑定的套接字设置为监听状态,保持一个LISTEN的状态
backlog的作用是用来设置当前最多能允许多少远端套接字在监听队列中排队。
5、accept()
#include/* See NOTES */ #include int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
accept的作用是将监听到的远端套接字accept到然后返回一个新的文件操作符,可以想象,如果不停的listen到则会有很多的文件操作符返回,再用一些方法便可以实现一对多的链接了,回到前面一个博客的问题,这时候如果大量远端套接字被accept到,然后服务器主动断开链接,大量的TIME_WAIT状态就是在这个时候产生的,(解决方法见上一篇博客)。
函数中的addr和addrlen即时输入型参数也是输出型参数,因为我们要获取远端的套接字信息
6、connect()
#include/* See NOTES */ #include int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
很显然这是一个客户端需要的一个函数,它用来链接远端的服务器。
下面是实现的代码:
分了三种形式来实现,一对一,一对多(多进程)和一对多(多线程)。
服务器端:
1 #include2 #include 3 #include 4 #include 5 #include 6 #include 7 #include 8 #include 9 #include 10 #include 11 #include 12 #include 13 14 using namespace std; 15 16 //下面是在多进程下注册的一个信号处理函数,能使得父进程不用 //再阻塞式或者轮询式的等待子进程的退出,当有子进程退出时会 //发送SIGCHLD信号我们捕捉这个信号后对其回收。 17 void hander(int sign) 18 { 19 int status=0; 20 while(waitpid(-1,&status,WNOHANG)>0) 21 { 22 int cur1=(status>>8)&0xff;//取低八位的信号 23 int cur2=status&0xff;//取高八位的退出码 24 cout<<"a child leave.."<<"signal::"< 0){ 58 buf[_size]='\0'; 59 }else if(_size<0){ 60 cout< 0) 101 { 102 buf[_size]='\0'; 103 }else if(_size==0){ 104 cout<<"leave..."<<"ip::"< 0) 122 { 123 buf[_size]='\0'; 124 }else if(_size ==0){ 125 cout<<"leave..."<<"ip::"< (几处地方复制过来缩进有点问题,注释时复制过来后来添加的)。
客户端
客户端需要一个套接字,因为服务器需要知道你的信息,但他不需要绑定,它只关心链接到服务器端即可。
1 #include2 #include 3 #include 4 #include 5 #include 6 #include 7 #include 8 #include 9 #include 10 #include 11 #include 12 using namespace std; 13 14 int main(int argc,char*argv[]) 15 { 16 if(argc!=3) 17 { 18 cout<<"please input::"<<"ip[]"<<"port[]"< >msg; 42 write(sock,msg.c_str(),msg.size()); 43 } 44 return 0; 45 } 46
总结:虽然实现了一个一对多的服务器,但是弊端是很容易发现的,首先,不管是多线程还是多进程的服务器,它的资源消耗是很大的,再说说多线程和多进程的缺点,当连接数目非常大的时候,cpu的调度也会变的很麻烦,虽然说cpu的执行速度非常快,但当成千上万个进程或者线程在运行的时候它也是会吃不消的。