socket:ios
TCP/IP協議中一個端口號和一個IP地址綁定在一塊兒就生成一個socket就表示了網絡中惟一的一個進程,它是全雙工的工做方式。編程
基於TCP的socket編程服務器
函數的使用:網絡
一、socket()多線程
#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int socket(int domain, int type, int protocol);
socket它用於建立一個套接字,返回值是一個文件描述符。dom
domin表示該套接字網絡層所用的協議,由於咱們用的是iPv4協議,因此這裏填AF_INET。socket
type表示該套接字傳輸層使用什麼模式傳輸,TCP是字節流的,因此這裏填SOCK_STREAM。tcp
protocol填寫爲0,系統會根據你以前的參數把它設置爲相應字段,好比咱們參數設置爲AF_INET,SOCK_STREAM,0,後面的0會自動設置爲IPPROTO_TCP。ide
二、bind()函數
#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> 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結構在內存中對齊*/ };
三、網絡字節序:
一臺主機有大小端模式而在網絡中傳輸數據的時候須要遵循必定的規則,即發送端從地地址開始發送,接收端從低地址開始接受,若是兩臺主機的大小端模式不同,那麼就會致使數據的錯亂,tcp/ip協議中規定網絡數據流應該採用大段的字節序,即高地址高位。爲了方便就有了一系列轉化的函數方便使用。
#include <arpa/inet.h> uint32_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 <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> 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);
四、listen()
#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int listen(int sockfd, int backlog);
listen的做用主要是用來將當前已經綁定的套接字設置爲監聽狀態,保持一個LISTEN的狀態
backlog的做用是用來設置當前最多能容許多少遠端套接字在監聽隊列中排隊。
五、accept()
#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
accept的做用是將監聽到的遠端套接字accept到而後返回一個新的文件操做符,能夠想象,若是不停的listen到則會有不少的文件操做符返回,再用一些方法即可以實現一對多的連接了,回到前面一個博客的問題,這時候若是大量遠端套接字被accept到,而後服務器主動斷開連接,大量的TIME_WAIT狀態就是在這個時候產生的,(解決方法見上一篇博客)。
函數中的addr和addrlen即時輸入型參數也是輸出型參數,由於咱們要獲取遠端的套接字信息
六、connect()
#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
很顯然這是一個客戶端須要的一個函數,它用來連接遠端的服務器。
下面是實現的代碼:
分了三種形式來實現,一對一,一對多(多進程)和一對多(多線程)。
服務器端:
1 #include<iostream> 2 #include<sys/socket.h> 3 #include<sys/types.h> 4 #include<arpa/inet.h> 5 #include<string.h> 6 #include<string> 7 #include<errno.h> 8 #include<stdlib.h> 9 #include<stdio.h> 10 #include<sys/wait.h> 11 #include<signal.h> 12 #include<pthread.h> 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::"<<cur2<<"code::"<<cur1<<endl; 25 } 26 } 27 28 int Listen(string &ip,int port) 29 { 30 int sock=socket(AF_INET,SOCK_STREAM,0);//建立一個套接字 31 cout<<sock<<endl; 32 struct sockaddr_in server; 33 server.sin_family=AF_INET; 34 server.sin_port=htons(port); 35 server.sin_addr.s_addr=inet_addr(ip.c_str()); 36 int bindret=bind(sock,(struct sockaddr*)&server,sizeof(server)); //綁定到端口上,不然操做系統會分配一個隨機端口。 37 if(bindret<0) 38 { 39 cout<<strerror(errno)<<endl; 40 } 41 int listen_sock=listen(sock,10); //設置監聽狀態 42 if(listen_sock<0) 43 { 44 cout<<strerror(errno)<<endl; 45 exit(2); 46 } 47 return sock; 48 } 49 50 void* output(void*output_sock) //一個讀端的線程,設置這個線程的目的是爲了 //讓主線程不停地accept而不停地使用這個線程 //來讀數據 51 { 52 int sock=(int)output_sock; 53 char buf[1024]; 54 while(1) 55 { 56 ssize_t _size=read(sock,buf,sizeof(buf)-1); 57 if(_size>0){ 58 buf[_size]='\0'; 59 }else if(_size<0){ 60 cout<<strerror(errno)<<endl; 61 }else{ 62 cout<<"leave..."<<endl; 63 break; 64 } 65 cout<<"client ::"<<buf; 66 fflush(stdout); 67 } 68 } 69 70 int main(int argc,char *argv[]) 71 { 72 if(argc!=3) //命令行參數格式設置爲 ./server ip port的格式 73 { 74 cout<<"please output like this"<<"argv[0]" <<"ip"<<"port"; 75 exit(1); 76 } 77 string ip(argv[1]); 78 int port = atoi(argv[2]); 79 int listen_sock=Listen(ip,port); 80 struct sockaddr_in client; //這個client的做用是爲了獲取遠端的套接字信息 81 socklen_t len=sizeof(client); 82 while(1) 83 { 84 int output_sock=accept(listen_sock,(struct sockaddr*)&client,&len); //從監聽隊列中獲取一個遠端套接字信息,並新生成一個文件描述符。 85 if(output_sock<0) 86 { 87 continue; 88 } 89 char buf[1024]; 90 cout<<"get connect..."<<"ip::"<<inet_ntoa(client.sin_addr)<<"port\ 91 ::"<<ntohs(client.sin_port)<<endl; 92 93 #ifdef _TEST1_ //條件編譯(在Makefile中gcc -o $@ $^ 後面加上-D_TEST1_便可完 // 成條件編譯) //TEST1完成的是一對一。這種模式顯然有很大的缺點。只能知足一個 //用戶的服務器顯然沒有什麼用處 94 //one connect 95 cout<<"TEST1"<<endl; 96 while(1) 97 { 98 memset(buf,'\0',sizeof(buf)); 99 ssize_t _size=read(output_sock,buf,sizeof(buf)-1); 100 if(_size>0) 101 { 102 buf[_size]='\0'; 103 }else if(_size==0){ 104 cout<<"leave..."<<"ip::"<<inet_ntoa(client.sin_addrr)<<endl; 105 break; 106 }else{ 107 108 cout<<strerror(errno)<<endl; 109 } 110 cout<<"client::"<<buf; 111 } 112 #elif _TEST2_ //TEST2實現的是一個多進程的一對多的服務器,雖然解決了多人的連接問題, //可是進程所佔用的資源是很大的。 113 // fork 114 cout<<"TEST2"<<endl; 115 pid_t id=fork() 116 if(id==0){ 117 close(listen_sock); //子進程不須要listen_sock。只須要讀數據就好了。 118 while(1) 119 { 120 ssize _size=read(output_sock,buf,sizeof(buf)-1); 121 if(_size>0) 122 { 123 buf[_size]='\0'; 124 }else if(_size ==0){ 125 cout<<"leave..."<<"ip::"<<inet_ntoa(client.sin_addr)<<end 126 break; 127 }else{ 128 cout<<strerror(errno)<<endl; 129 } 130 } 131 close(output_sock); 132 }else if(id<0){ 133 cout<<strerror(errno)<<endl; 134 }else{ //父進程只須要關心listen_sock; 135 close(output_sock); 136 signal(SIGCHLD,hander); 137 } 138 #elif _TEST3_ //實現的是一個多線程一對多的服務器,一樣線程也有開銷。 139 //pthread 140 cout<<"TEST3"<<endl; 141 pthread_t id; 142 pthread_create(&id,NULL,output,(void*)output_sock); 143 pthread_detach(id); 144 #else 145 cout<<"default"<<endl; 146 #endif 147 } 148 return 0; 149 }
(幾處地方複製過來縮進有點問題,註釋時複製過來後來添加的)。
客戶端
客戶端須要一個套接字,由於服務器須要知道你的信息,但他不須要綁定,它只關心連接到服務器端便可。
1 #include<iostream> 2 #include<sys/socket.h> 3 #include<sys/types.h> 4 #include<arpa/inet.h> 5 #include<string.h> 6 #include<string> 7 #include<errno.h> 8 #include<stdlib.h> 9 #include<sys/wait.h> 10 #include<signal.h> 11 #include<pthread.h> 12 using namespace std; 13 14 int main(int argc,char*argv[]) 15 { 16 if(argc!=3) 17 { 18 cout<<"please input::"<<"ip[]"<<"port[]"<<endl; 19 } 20 int port=atoi(argv[2]); 21 const string server_ip=argv[1]; 22 int sock=socket(AF_INET,SOCK_STREAM,0); 23 if(sock==0) 24 { 25 cout<<strerror(errno)<<endl; 26 exit(1); 27 } 28 struct sockaddr_in client; 29 client.sin_family=AF_INET; 30 client.sin_port=htons(port); 31 client.sin_addr.s_addr=inet_addr(server_ip.c_str()); 32 int consock=connect(sock,(struct sockaddr*)&client,sizeof(sockaddr_in)); //連接到目的套接字,並把本身的套接字信息發送給對方 33 if(consock<0) 34 { 35 cout<<strerror(errno)<<endl; 36 } 37 string msg; 38 while(1) 39 { 40 cout<<"please write msg"<<endl; 41 cin>>msg; 42 write(sock,msg.c_str(),msg.size()); 43 } 44 return 0; 45 } 46
總結:雖然實現了一個一對多的服務器,可是弊端是很容易發現的,首先,不論是多線程仍是多進程的服務器,它的資源消耗是很大的,再說說多線程和多進程的缺點,當鏈接數目很是大的時候,cpu的調度也會變的很麻煩,雖說cpu的執行速度很是快,但當成千上萬個進程或者線程在運行的時候它也是會吃不消的。