socket通訊——TCP

一、socket數組

      每一條TCP鏈接兩個端點,TCP鏈接的端點交錯socket服務器

      socket=(IP地址:端口號)———>標示了網絡上惟一的一個進程網絡

      每一條TCP鏈接被兩個socket(即socket pair)惟一的肯定。socket

       TCP鏈接={socket1,socket2}={{IP1,port1},{IP2,port2}}tcp

二、網絡字節序 ide

1)大端:大端放在該值的起始地址;一個數據的高字節放在起始地址函數

2)小端:小端放在該值的起始地址;一個數據的低字節放在起始地址性能

3)數組:數組元素是按照元素個數開闢完空間,將第一個元素放在低地址,而第一個元素若是是大端,就將其高字節放在元素的起始地址處spa

    wKioL1eZpBnyRUWGAADi8omVFpI700.png

4)網絡字節序線程

     網絡字節序是大端的,讀的時候認爲讀到的第一個字節是高字節;因此小端要轉換成大端

     wKiom1eZpCzi0LV3AADVVQHM80Q722.png

    發數據:從低地址——>高地址          ;收數據:從低地址——>高地址

                    主機字節序——網絡字節序                    網絡字節序——主機字節序

三、socket地址的數據類型

wKioL1eZpG2yCszDAAC1q1-xjB8729.png

各類socket地址結構體的開頭都是相同的,前16位表示整個結構體的長度(並非全部UNIX的實現 都有長度字段,如Linux就沒有),後16位表示地址類型。


四、TCP

   wKioL1eZpf3A8lN0AAMhbrib52U296.png

幾個須要注意的點:

          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上。



wKiom1eZtnzwhWHbAAByvR90zZo771.png

#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代碼同上)

wKiom1eZyRLwgo2aAAB9AjCUEPo731.png

#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跑起來

wKioL1eZqEzwYmtJAAApDFGZ8nc375.png

wKiom1eZqEzi5rmRAAA2aHnKIPo272.png

狀況是這個的用netstat -nltp查看tcp服務

wKioL1eZqH7A8mQnAABm_sXQTP4795.png

這個服務就是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關閉

wKiom1eZqTTz5vJlAAA4GqImjtY831.png

他說綁定的端口已經被佔用了,那是哪一個端口不能用呢,就是第一次socket綁定的8080不能用;

其實咱們想作到的是關閉accept這個鏈接,對於listen的socket並不想關閉;也就是說咱們只不過是不想鏈接了可是咱們仍是須要繼續監聽的,這兩個行爲佔用的端口號相同,可是IP地址不一樣,創建鏈接的時候須要具體的IP地址,而監聽的時候用的IP地址是網絡地址。

這裏用一個函數能夠作到 setsockopt()設置socket描述符的 選項SO_REUSEADDR1,表 容許建立端 號相同但IP地址不一樣的多個socket描述符。