一、socket數組
每一條TCP鏈接兩個端點,TCP鏈接的端點交錯socket服務器
socket=(IP地址:端口號)———>標示了網絡上惟一的一個進程網絡
每一條TCP鏈接被兩個socket(即socket pair)惟一的肯定。socket
TCP鏈接={socket1,socket2}={{IP1,port1},{IP2,port2}}tcp
二、網絡字節序 ide
1)大端:大端放在該值的起始地址;一個數據的高字節放在起始地址函數
2)小端:小端放在該值的起始地址;一個數據的低字節放在起始地址性能
3)數組:數組元素是按照元素個數開闢完空間,將第一個元素放在低地址,而第一個元素若是是大端,就將其高字節放在元素的起始地址處spa
4)網絡字節序線程
網絡字節序是大端的,讀的時候認爲讀到的第一個字節是高字節;因此小端要轉換成大端
發數據:從低地址——>高地址 ;收數據:從低地址——>高地址
主機字節序——網絡字節序 網絡字節序——主機字節序
三、socket地址的數據類型
各類socket地址結構體的開頭都是相同的,前16位表示整個結構體的長度(並非全部UNIX的實現 都有長度字段,如Linux就沒有),後16位表示地址類型。
四、TCP
幾個須要注意的點:
1)socket中的數據格式是:SOCK_STREAM
2) bind以前須要將容器struct sockaddr_in 進行初始化:
端口號:將主機序列轉成網絡序列;
IP地址:字符串轉成×××
3)int listen(sockfd,backlog) 須要瞭解backlog是什麼意思
listen狀態下能夠監聽多個client,因此能夠backlog個client處於鏈接等待狀態,超過
backlog個的就忽略
4)accept()用於被動等待鏈接,其中有兩個參數須要注意
int accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen)
addr:是一個傳出參數,該函數返回時,將client的端口號和IP地址填充進去若是
addr=NULL,表示不關心addr的地址
addrlen:是一個傳入傳出參數,進去的時候是定義的addr結構體的大小,傳出的是實
際client的addr 的大小(有可能實際的小於定義的)
5)client端不須要bind函數去綁定,也不建議bind,可是常規上是能夠bind的
6)connect鏈接的時候填充的是server的地址
代碼1:單server——單client
1)server.c
思考:在客戶端的read函數要有正確的處理
若是read的返回值:大於0,說明讀到數據了,就打印在屏幕上;
等於0,說明服務器沒有再給客戶端發信息,要關閉客戶端
(下面說明這種狀況,服務器先主動關閉後,向客戶端發送FIN,客戶端發送ACK相應,而後客戶端繼續發信息,服務器會以RST響應,這個時候若是客戶端繼續調用read,因爲客戶端已經收到了服務器的FIN,因此read會返回0)
若是客戶端對於read的返回值沒有進行處理,繼續發信息給服務器,這個時候若是客戶端在已經收到RST的狀況下,想要調用write函數,內核會發送一個SIGPIPE信號,來終止進程,因此這個信號也須要捕獲處理
若是在read以前,服務器就崩潰了,read的會阻塞等待,那麼它會等很長時間纔會放棄鏈接
#include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <string.h> #include <unistd.h> const short _PORT=8080; #define _IP "127.0.0.1" int main() { //1.create socket int listenfd=socket(AF_INET,SOCK_STREAM,0); if(listenfd<0) { perror("socket"); return 1; } //2.1initial sockaddr_in struct sockaddr_in local; local.sin_family=AF_INET; local.sin_port=htons(_PORT); local.sin_addr.s_addr=inet_addr(_IP); if(bind(listenfd,(struct sockaddr*)&local,sizeof(local))<0) { perror("bind"); return 2; } int ret=listen(listenfd,5); if(ret<0) { perror("listen"); return 3; } while(1) { struct sockaddr_in peer; socklen_t len=sizeof(peer); int fd=accept(listenfd,(struct sockaddr *)&peer,&len); if(fd<0) { perror("accept"); return 4; } else{ char buf[1024]; while(1) { memset(buf,'\0',1024); ssize_t _s=read(fd,buf,sizeof(buf)-1); if(_s>0) { //printf("client# %s",buf); buf[_s]='\0'; write(fd,buf,sizeof(buf)-1); printf("client# %s",buf); // write(fd,buf,sizeof(buf)-1); } else if(_s==0) { printf("read done ...\n"); break; } else{ break; } } close(fd); break; } } return 0; }
2)client.c
#include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <unistd.h> #include <string.h> const short _PORT=8080; const char *_IP="127.0.0.1"; int main() { //1creat socket int fd=socket(AF_INET,SOCK_STREAM,0); if(fd<0) { perror("socket"); return 1; } struct sockaddr_in remote; remote.sin_family=AF_INET; remote.sin_port=htons(_PORT); remote.sin_addr.s_addr=inet_addr(_IP); int ret=connect(fd,(struct sockaddr *)&remote,sizeof(remote)); if(ret<0) { perror("connect"); return 2; } char buf[1024]; while(1) { memset(buf,'\0',sizeof(buf)); printf("Please Enter:\n"); ssize_t _s=read(0,buf,sizeof(buf)-1); if(_s>0) { buf[_s]='\0'; write(fd,buf,strlen(buf)); memset(buf,'\0',sizeof(buf)); ssize_t _ss=read(fd,buf,sizeof(buf)-1); if(_ss>0) { buf[_ss]='\0'; printf("%s",buf); } else { break; } } } return 0; }
代碼2:用fork子進程實現 單server——多client (client代碼同上,須要修改的只有server端)
總結:
(1)子進程能夠關閉沒必要要的文件描述符或者釋放其餘資源,由於使用fork後,若是子進程不調用exec以使用新的進程空間的話,子進程會複製父進程的進程空間內容,包括數據段等(代碼段是共享的,數據段等採用一種寫時複製的策略來提升性能)。因此沒必要要的資源能夠儘快釋放。
(2)因爲子進程在退出後會成爲殭屍進程,這些信息會遺留在父進程內,因此父進程註冊所監聽的信號及其處理函數(SIGCHLD是子進程退出時向父進程發送的信號,默認狀況下是不採起任何動做,因此咱們須要調用wait或者waitpid來完全清除子進程。)較爲經常使用的信號註冊和信號處理能夠在fork前調用,使得子進程也能夠完成一樣的功能。
(3)最後父進程要關閉已經accept返回的socket文件描述符,由於父進程不處理相應活動,而是交由fork出來的子進程來處理。
(4)使用fork的時候,要注意它對父子進程有執行順序是不作任何限制的(vfork的話會先運行子進程),因此必要的時候可讓進程睡眠或者用消息喚醒。
使用fork會有如下問題:
1 fork是昂貴的,內存映像要從父進程拷貝到子進程,全部描述字要在子進程中複製等等,還有進程的上下文切換開銷。
2 fork子進程後,須要用 進程間通訊IPC 在父子進程之間傳遞信息,特別是fork內的信息。
信號處理函數實現以下:
void sig_child(int signo) //父進程對子進程結束的信號處理
{
pid_t pid;
int stat;
while( (pid=waitpid(-1,&stat,WNOHANG))>0)
printf("child %d terminated\n",pid);
return;
}
這樣一來咱們就能夠應付子進程退出後的殘留信息問題。注意這裏使用waitpid,其中使用WNOHANG參數來防止父進程阻塞在wait上。
#include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <string.h> #include <unistd.h> #include <stdlib.h> const short _PORT=8080; #define _IP "127.0.0.1" int main() { int listenfd=socket(AF_INET,SOCK_STREAM,0); if(listenfd<0) { perror("socket"); return 1; } struct sockaddr_in local; local.sin_family=AF_INET; local.sin_port=htons(_PORT); local.sin_addr.s_addr=inet_addr(_IP); if(bind(listenfd,(struct sockaddr *)&local,sizeof(local))<0) { perror("bind"); return 2; } if(listen(listenfd,5)<0) { perror("listen"); return 3; } while(1) { struct sockaddr_in peer; socklen_t len=sizeof(peer); int fd=accept(listenfd,(struct sockaddr*)&peer,&len); int id=fork(); if(id==0) { close(listenfd); while(1) { char buf[1024]; memset(buf,'\0',sizeof(buf)); ssize_t _s=read(fd,buf,sizeof(buf)-1); if(_s>0) { buf[_s]='\0'; printf("client#%s\n",buf); write(fd,buf,sizeof(buf)-1); } } close(fd); exit(0); }else{ close(fd); } } return 0; }
代碼3:用線程實現 單server——多client(client代碼同上)
#include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <string.h> #include <unistd.h> #include <stdlib.h> #include <pthread.h> const short _PORT=8080; #define _IP "127.0.0.1" void *run(void *arg) { char buf[1024]; int fd=(int)arg; while(1) { memset(buf,'\0',sizeof(buf)); ssize_t _s=read(fd,buf,sizeof(buf)-1); if(_s>0) { buf[_s]='\0'; write(fd,buf,strlen(buf)); printf("client# %s",buf); } else if(_s==0) { printf("read done...\n"); close(fd); break; } else break; } exit(0); } int main() { int listenfd=socket(AF_INET,SOCK_STREAM,0); if(listenfd<0) { perror("socket"); return 1; } struct sockaddr_in local; local.sin_family=AF_INET; local.sin_port=htons(_PORT); local.sin_addr.s_addr=inet_addr(_IP); if(bind(listenfd,(struct sockaddr *)&local,sizeof(local))<0) { perror("bind"); return 2; } if(listen(listenfd,5)<0) { perror("listen"); return 3; } while(1) { struct sockaddr_in peer; socklen_t len=sizeof(peer); int fd=accept(listenfd,(struct sockaddr *)&peer,&len); if(fd>0) { pthread_t id; // int ret=pthread_create(&id,NULL,run,(void *)fd); int ret=pthread_create(&id,NULL,run,(void*)fd); if(ret<0) { perror("pthread_create"); } //pthread_detach(id); } } close(listenfd); return 0; }
四、關於bind:Address Already in use的解決方案及分析
當./server跑起來./client跑起來
狀況是這個的用netstat -nltp查看tcp服務
這個服務就是server端的服務
這個時候咱們在./server那個終端窗口下輸入 ctl+c
這個時候./server終止屬於四次揮手中主動關閉的那一我的,意味着他在發送最後一個ACK後還要經歷一個time_wait的狀態等待2MSL。
1)若是在關閉以後在./server關閉./client以後當即運行就會發現這個一個現象-------TIME_WAIT
這裏的緣由就是time_wait:須要等待2msl的時間
2)若是隻關閉了./server以後在輸入./server (./client沒有被終止)------FIN_WAIT2
這裏的緣由是隻完成了半關閉,./server一直在等./client關閉
他說綁定的端口已經被佔用了,那是哪一個端口不能用呢,就是第一次socket綁定的8080不能用;
其實咱們想作到的是關閉accept這個鏈接,對於listen的socket並不想關閉;也就是說咱們只不過是不想鏈接了可是咱們仍是須要繼續監聽的,這兩個行爲佔用的端口號相同,可是IP地址不一樣,創建鏈接的時候須要具體的IP地址,而監聽的時候用的IP地址是網絡地址。
這裏用一個函數能夠作到 setsockopt()設置socket描述符的 選項SO_REUSEADDR爲1,表 容許建立端 號相同但IP地址不一樣的多個socket描述符。