關於TCP編程,你是否爲此迷惑過


TCP的服務端編程,一般API的調用順序是socket, bind, listen, accept, send, recv等。TCP的客戶端編程,一般API的調用順序是socket, connect, send, recv。

在服務端,accept函數的其中一個入參是listen-socket,會返回一個新的connection-socket。通過connection-socket,調用getpeername,可以得到客戶端的IP和端口。通過connection-socket,調用getsockname,可以得到本地的IP和端口。accept函數的其中一個出參,也可以返回客戶端的IP和端口。

通常如果客戶端創建socket後,調用connect前,沒有調用bind來綁定本地的IP和端口,那麼connect建立的連接的本端IP是API自動通過目的IP選擇的,而本端端口是隨機的。這沒有問題。通過accept函數返回的connection-socket,調用getsockname返回的IP是服務器端listen的IP。這也沒問題。問題是,通過accept函數返回的connection-socket,調用getsockname返回的端口是什麼呢?

按我直觀的、Intuitive的理解,這個端口應該是個隨機的端口,因爲connection-socket是新建的socket,是和listen-socket不同的socket。但事實令我不解,這個端口竟然和listen-socket綁定的端口是相同的,不知你是否也跟我一樣想法過。上代碼。

/* Sample TCP server */

/* www.cs.ucsb.edu/~almeroth/classes/W01.176B/hw2/examples/tcp-server.c */

#include <sys/socket.h>

#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <strings.h>

#include <stdio.h>

#include <stdlib.h>
#include <unistd.h>


int main(int argc, char**argv)

{

   int listenfd,connfd,n;

   struct sockaddr_in servaddr,cliaddr,localaddr;

   socklen_t clilen;

   socklen_t locallen;

   pid_t     childpid;

   char mesg[1000];



   listenfd=socket(AF_INET,SOCK_STREAM,0);



   bzero(&servaddr,sizeof(servaddr));

   servaddr.sin_family = AF_INET;

   servaddr.sin_addr.s_addr=htonl(INADDR_ANY);

   servaddr.sin_port=htons(32000);

   bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr));



   listen(listenfd,1024);



   for(;;)

   {

      clilen=sizeof(cliaddr);

      connfd = accept(listenfd,(struct sockaddr *)&cliaddr,&clilen);

      locallen=sizeof(localaddr);

      getsockname(connfd,(struct sockaddr*)&localaddr,&locallen);

      printf("local addr=%X\n",htonl(*((unsigned int*)&localaddr.sin_addr)));

      printf("local port=%d\n",htons(localaddr.sin_port));

      if ((childpid = fork()) == 0)

      {

         close (listenfd);



         for(;;)

         {

            n = recvfrom(connfd,mesg,1000,0,(struct sockaddr *)&cliaddr,&clilen);

            sendto(connfd,mesg,n,0,(struct sockaddr *)&cliaddr,sizeof(cliaddr));

            printf("-------------------------------------------------------\n");

            mesg[n] = 0;

            printf("Received the following:\n");

            printf("%s",mesg);

            printf("-------------------------------------------------------\n");

         }

         

      }

      close(connfd);

   }

}
/* Sample TCP client */

/* www.cs.ucsb.edu/~almeroth/classes/W01.176B/hw2/examples/tcp-client.c */

#include <sys/socket.h>

#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <strings.h>

#include <stdio.h>
#include <stdlib.h>



int main(int argc, char**argv)

{

   int sockfd,n;

   struct sockaddr_in servaddr,cliaddr;

   char sendline[1000];

   char recvline[1000];



   if (argc != 2)

   {

      printf("usage:  client <IP address>\n");

      exit(1);

   }



   sockfd=socket(AF_INET,SOCK_STREAM,0);



   bzero(&servaddr,sizeof(servaddr));

   servaddr.sin_family = AF_INET;

   servaddr.sin_addr.s_addr=inet_addr(argv[1]);

   servaddr.sin_port=htons(32000);



   connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));



   while (fgets(sendline, 10000,stdin) != NULL)

   {

      sendto(sockfd,sendline,strlen(sendline),0,

             (struct sockaddr *)&servaddr,sizeof(servaddr));

      n=recvfrom(sockfd,recvline,10000,0,NULL,NULL);

      recvline[n]=0;

      fputs(recvline,stdout);

   }

}

這兩個tcp-server和tcp-client程序是我從www.cs.ucsb.edu借鑑過來的,我只加了getsockname相關部分。服務端程序執行結果:

$ ./tcpserver 
local addr=7F000001
local port=32000
-------------------------------------------------------
Received the following:
abcdef
-------------------------------------------------------
-------------------------------------------------------
Received the following:
ghijkl
-------------------------------------------------------
^C
客戶端程序執行結果:

$ ./tcpclient 127.0.0.1
abcdef
abcdef
ghijkl
ghijkl
^C
看到了吧,accept函數返回的connection-socket,調用getsockname返回的端口竟然和listen-socket的端口是相同的,都是32000。這是爲什麼呢?爲什麼這樣counter-intuitive呢?

問了Google,問了對TCP有研究的高人,他又問了高人,才明白。原來我們看socket,不能光看到通過socke API或accept API創建的socket,心中要有圖畫,這個socket是和遠方連接的。每個socket由四元組組成,本地IP,本地端口,遠方IP,遠方端口。雖然accept函數返回的connection-socket調用getsockname返回的IP和端口同listen-socket綁定的IP和端口是相同的,但因爲他們的遠方連接的不同,所以他們是不同的socket。不要迷惑。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章