【socket筆記】TCP、UDP通訊總結

  • 2019 年 11 月 28 日
  • 筆記

一、什麼是socket?

Socket的英文原義是「孔」或「插座」。在編程中,Socket被稱做套接字,是網路通訊中的一種約定。Socket編程的應用無處不在,我們平時用的QQ、微信、瀏覽器等程式,都與Socket編程有關。我們平時使用瀏覽器查資料,這個過程的技術原理是怎樣的呢?

我們平時使用瀏覽器,大致就是這樣的一個過程。這裡有兩個重要的名詞:服務端與客戶端。

Socket編程的目的就是如何實現這兩端之間的通訊。

1、Socket編程在嵌入式中也很重要

Socket編程不僅僅在互聯網方面很重要,在我們的嵌入式方面也是非常的重要,因為現在很多電子設備都趨向於聯網。比如很多嵌入式工作的招聘要求都會有這一條要求:

說一點題外話,還在學校的朋友,如果感覺到很迷茫,不知道學什麼的時候,可以上招聘網站上看看自己未來工作相關的職位的任職要求,這樣就可以總結自己的一些不足、比較有針對性的去學習。

二、Socket編程中的幾個重要概念

Socket編程用於解決我們客戶端與服務端之間通訊的問題。我們平時多多少少都有聽過IP地址、埠、TCP協議、UDP協議等概念,這些都與Socket編程中相關,想要知道怎麼用起來,當然得先了解它們的一些介紹。下面看一下這些專業術語的一些要點介紹:

1、什麼是IP地址?

IP地址(Internet Protocol Address)是指互聯網協議地址,又譯為網際協議地址。IP地址被用來給Internet上的電腦一個編號。我們可以把「個人電腦」比作「一台電話」,那麼「IP地址」就相當於「電話號碼」。若電腦1知道電腦2的IP地址,則電腦1就能訪問電腦2。

IP地址是一個32位的二進位數,通常被分割為4個「8位二進位數」(也就是4個位元組)。IP地址通常用點分十進位表示成(a.b.c.d)的形式,其中,a,b,c,d都是0~255之間的十進位整數。例:點分十進IP地址(100.4.5.6),實際上是32位二進位數(01100100.00000100.00000101.00000110)。

IP地址有IPv4與IPv6之分,現在用得較多的是IPv4。其中,有一個特殊的IP地址需要我們記住:127.0.0.1,這是回送地址,即本地機,一般用來測試使用。後邊我們的實例中會用到。

關於IP地址還有很多知識要點,但是對於在Socket編程中的應用,我們暫且知道這麼多就可以。

2、什麼是TCP/IP埠?

上一點中我們提到,若電腦1知道電腦2的IP地址,則電腦1就能訪問電腦2。但是,我們要訪問電腦2中的不同的應用軟體,則還得需要一個資訊:埠。埠使用16bit進行編號,即其範圍為:0~65536。但0~1023 的埠一般由系統分配給特定的服務程式,例如 Web 服務的埠號為 80,FTP 服務的埠號為 21等。

3、什麼是協議?

協議(Protocol)是通訊雙方進行數據交互的一種約定。如TCP、UDP協議:

(1)TCP協議

TCP(Transmission Control Protocol 傳輸控制協議)是一種面向連接的、可靠的、基於位元組流的傳輸層通訊協議,數據可以準確發送,數據丟失會重發。TCP協議常用於web應用中。

TCP連接(三次握手)

TCP傳輸起始時,客戶端、服務端要完成三次數據交互工作才能建立連接,常稱為三次握手。可形象比喻為如下對話:

客戶端:服務端您好,我有數據要發給你,請求您開通訪問許可權。 服務端:客戶端您好,已給您開通許可權,您可以發送數據了。 客戶端:收到,謝謝。

具體示意圖為:

這裡的SYN和ACK是都是標誌位,其中SYN代表新建一個連接,ACK代表確認。其中m、n都是隨機數。具體說明如:

  • 第一次握手:SYN標誌位被置位,客戶端向服務端發送一個隨機數m。
  • 第二次握手:ACK、SYN標誌位被置位。服務端向客戶端發送m+1表示確認剛才收到的數據,同時向客戶端發送一個隨機數n。
  • 第三次握手:ACK標誌被置位。客戶端向服務端發送n+1表示確認收到數據。
TCP斷開(四次揮手)

TCP斷開連接時,客戶端、服務端要完成四次數據交互工作才能建立連接,常稱為四次揮手。可形象比喻為如下對話:

客戶端:服務端您好,我發送數據完畢了,即將和您斷開連接。 服務端:客戶端您好,我稍稍準備一下,再給您斷開 服務端:客戶端您好,我準備好了,您可以斷開連接了。 客戶端:好的,合作愉快!

具體示意圖為:

這裡的FIN也是一個標誌位,代表斷開連接。具體說明類似三次握手。

為什麼建立連接只需要三次數據交互,而斷開連接需要四次呢?

建立連接時,服務端在監聽狀態下,收到建立連接請求的SYN報文後,把ACK和SYN放在一個報文里發送給客戶端。

而關閉連接時,當收到對方的FIN報文時,僅僅表示對方不再發送數據了但是還能接收數據,己方也未必全部數據都發送給對方了,所以己方可以立即close,也可以發送一些數據給對方後,再發送FIN報文給對方來表示同意現在關閉連接,因此,己方ACK和FIN一般都會分開發送。

(2)UDP協議

UDP(User Datagram Protocol, 用戶數據報協議)是一種無連接的傳輸層協議,提供面向事務的簡單不可靠資訊傳送服務,可以保證通訊效率,傳輸延時小。例如影片聊天應用中用的就是UDP協議,這樣可以保證及時丟失少量數據,影片的顯示也不受很大影響。

4、什麼是協議族?

協議族是多個協議的統稱。比如我們的TCP/IP協議族,其不僅僅是TCP協議、IP協議,而是多個協議的集合,其包含IP、TCP、UDP、FTP、SMTP等協議。

三、socket編程的API介面

1、Linux下的socket API介面

(1)創建socket:socket()函數

函數原型:

int socket(int af, int type, int protocol);
  • af參數:af 為地址族(Address Family),也就是 IP 地址類型,常用的有 AF_INET 和 AF_INET6,其前綴也可以是PF(Protocol Family),即PF_INET 和 PF_INET6。
  • type參數:type 為數據傳輸方式,常用的有 面向連接(SOCK_STREAM)方式(即TCP) 和 無連接(SOCK_DGRAM)的方式(即UDP)。
  • protocol參數:protocol 表示傳輸協議,常用的有 IPPROTO_TCP 和 IPPTOTO_UDP,分別表示 TCP 傳輸協議和 UDP 傳輸協議。

使用示例:

創建TCP套接字:

int tcp_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

創建UDP套接字:

int udp_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);

(2)綁定套接字:bind()函數

函數原型:

int bind(int sock, struct sockaddr *addr, socklen_t addrlen); 
  • sock參數:sock 為 socket 文件描述符。
  • addr參數:addr 為 sockaddr 結構體變數的指針。
  • addrlen參數:addrlen 為 addr 變數的大小,可由 sizeof() 計算得出。

使用示例:

將創建的套接字ServerSock與本地IP127.0.0.1、埠1314進行綁定:

/* 創建服務端socket */  int ServerSock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);    /* 設置服務端資訊 */  struct sockaddr_in ServerSockAddr;  memset(&ServerSockAddr, 0, sizeof(ServerSockAddr));     // 給結構體ServerSockAddr清零  ServerSockAddr.sin_family = PF_INET;                      // 使用IPv4地址  ServerSockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");// 本機IP地址  ServerSockAddr.sin_port = htons(1314);                  // 埠    /* 綁定套接字 */  bind(ServerSock, (SOCKADDR*)&ServerSockAddr, sizeof(SOCKADDR));

其中struct sockaddr_in類型的結構體變數用於保存IPv4的IP資訊。若是IPv6,則有對應的結構體:

struct sockaddr_in6  {      sa_family_t sin6_family;    // 地址類型,取值為AF_INET6      in_port_t sin6_port;         // 16位埠號      uint32_t sin6_flowinfo;     // IPv6流資訊      struct in6_addr sin6_addr;  // 具體的IPv6地址      uint32_t sin6_scope_id;     // 介面範圍ID  };

(3)建立連接:connect()函數

函數原型:

int connect(int sock, struct sockaddr *serv_addr, socklen_t addrlen);  

參數與bind()的參數類似。

使用示例:

int ClientSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);  connect(ClientSock, (SOCKADDR*)&ServerSockAddr, sizeof(SOCKADDR));

(4)監聽:listen()函數

函數原型:

int listen(int sock, int backlog);
  • sock參數:sock 為需要進入監聽狀態的套接字。
  • backlog參數:backlog 為請求隊列的最大長度。

使用示例:

/* 進入監聽狀態 */  listen(ServerSock, 10);

(5)接收請求:accept()函數

函數原型:

int accept(int sock, struct sockaddr *addr, socklen_t *addrlen);
  • sock參數:sock 為伺服器端套接字。
  • addr參數:addr 為 sockaddr_in 結構體變數。
  • addrlen參數:addrlen 為參數 addr 的長度,可由 sizeof() 求得。
  • 返回值:一個新的套接字,用於與客戶端通訊。

使用示例:

/* 監聽客戶端請求,accept函數返回一個新的套接字,發送和接收都是用這個套接字 */  int ClientSock = accept(ServerSock, (SOCKADDR*)&ClientAddr, &len);

(6)關閉:close()函數

函數原型:

int close(int fd);
  • fd:要關閉的文件描述符。

使用示例:

close(ServerSock);

(7)數據的接收和發送

數據收發函數有幾組:

  • read()/write()
  • recv()/send()
  • readv()/writev()
  • recvmsg()/sendmsg()
  • recvfrom()/sendto()

函數原型:

ssize_t read(int fd, void *buf, size_t count);  ssize_t write(int fd, const void *buf, size_t count);  ssize_t send(int sockfd, const void *buf, size_t len, int flags);  ssize_t recv(int sockfd, void *buf, size_t len, int flags);  ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,                        const struct sockaddr *dest_addr, socklen_t addrlen);  ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,                          struct sockaddr *src_addr, socklen_t *addrlen);  ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);  ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);

這裡介紹一下recv()/send()、recvfrom()/sendto()。

recv()函數:

ssize_t recv(int sockfd, void *buf, size_t len, int flags);
  • sockfd參數:sockfd為要接收數據的套接字。
  • buf參數:buf 為要接收的數據的緩衝區地址。
  • len參數:len 為要接收的數據的位元組數。
  • flags參數:flags 為接收數據時的選項,常設為0。

send()函數:

ssize_t send(int sockfd, const void *buf, size_t len, int flags);
  • sockfd參數:sockfd為要發送數據的套接字。
  • buf參數:buf 為要發送的數據的緩衝區地址。
  • len參數:len 為要發送的數據的位元組數。
  • flags參數:flags 為發送數據時的選項,常設為0。

recvfrom()函數:

ssize_t recvfrom(int sock, void *buf, size_t nbytes, int flags, struct sockadr *from, socklen_t *addrlen);
  • sock:用於接收UDP數據的套接字;
  • buf:保存接收數據的緩衝區地址;
  • nbytes:可接收的最大位元組數(不能超過buf緩衝區的大小);
  • flags:可選項參數,若沒有可傳遞0;
  • from:存有發送端地址資訊的sockaddr結構體變數的地址;
  • addrlen:保存參數 from 的結構體變數長度的變數地址值。

sendto()函數:

ssize_t sendto(int sock, void *buf, size_t nbytes, int flags, struct sockaddr *to, socklen_t addrlen);
  • sock:用於傳輸UDP數據的套接字;
  • buf:保存待傳輸數據的緩衝區地址;
  • nbytes:帶傳輸數據的長度(以位元組計);
  • flags:可選項參數,若沒有可傳遞0;
  • to:存有目標地址資訊的 sockaddr 結構體變數的地址;
  • addrlen:傳遞給參數 to 的地址值結構體變數的長度。

2、windows下的socket API介面

跟Linux下的差不多:

SOCKET socket(int af, int type, int protocol);  int bind(SOCKET sock, const struct sockaddr *addr, int addrlen);  int connect(SOCKET sock, const struct sockaddr *serv_addr, int addrlen);  int listen(SOCKET sock, int backlog);  SOCKET accept(SOCKET sock, struct sockaddr *addr, int *addrlen);  int closesocket( SOCKET s);  int send(SOCKET sock, const char *buf, int len, int flags);  int recv(SOCKET sock, char *buf, int len, int flags);  int recvfrom(SOCKET sock, char *buf, int nbytes, int flags, const struct sockaddr *from, int *addrlen);  int sendto(SOCKET sock, const char *buf, int nbytes, int flags, const struct sockadr *to, int addrlen);

3、TCP、UDP通訊的socket編程過程圖

(1)TCP通訊socket編程過程

(2)UDP通訊socket編程過程

四、socket的應用實例

1、基於TCP的本地客戶端、服務端資訊交互實例

本例的例子實現的功能為:本地TCP客戶端往本地TCP服務端發送數據,TCP服務端收到數據則會列印輸出,同時把原數據返回給TCP客戶端。這個例子類似於我們在做單片機的串口實驗時,串口上位機往我們的單片機發送數據,單片機收到數據則把該數據原樣返回給上位機。

(1)windows的程式:

服務端程式tcp_server.c:

#include <stdio.h>  #include <winsock2.h>    #define BUF_LEN  100    int main(void)  {      WSADATA wd;      SOCKET ServerSock, ClientSock;      char Buf[BUF_LEN] = {0};      SOCKADDR ClientAddr;      SOCKADDR_IN ServerSockAddr;      int addr_size = 0, recv_len = 0;        /* 初始化操作sock需要的DLL */      WSAStartup(MAKEWORD(2,2),&wd);        /* 創建服務端socket */      if (-1 == (ServerSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)))      {          printf("socket error!n");          exit(1);      }        /* 設置服務端資訊 */      memset(&ServerSockAddr, 0, sizeof(ServerSockAddr));     // 給結構體ServerSockAddr清零      ServerSockAddr.sin_family = AF_INET;                      // 使用IPv4地址      ServerSockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");// 本機IP地址      ServerSockAddr.sin_port = htons(1314);                  // 埠        /* 綁定套接字 */      if (-1 == bind(ServerSock, (SOCKADDR*)&ServerSockAddr, sizeof(SOCKADDR)))      {          printf("bind error!n");          exit(1);      }        /* 進入監聽狀態 */      if (-1 == listen(ServerSock, 10))      {          printf("listen error!n");          exit(1);      }        addr_size = sizeof(SOCKADDR);        while (1)      {          /* 監聽客戶端請求,accept函數返回一個新的套接字,發送和接收都是用這個套接字 */          if (-1 == (ClientSock = accept(ServerSock, (SOCKADDR*)&ClientAddr, &addr_size)))          {              printf("socket error!n");              exit(1);          }            /* 接受客戶端的返回數據 */          int recv_len = recv(ClientSock, Buf, BUF_LEN, 0);          printf("客戶端發送過來的數據為:%sn", Buf);            /* 發送數據到客戶端 */          send(ClientSock, Buf, recv_len, 0);            /* 關閉客戶端套接字 */          closesocket(ClientSock);            /* 清空緩衝區 */          memset(Buf, 0, BUF_LEN);      }        /*如果有退出循環的條件,這裡還需要清除對socket庫的使用*/      /* 關閉服務端套接字 */      //closesocket(ServerSock);      /* WSACleanup();*/        return 0;  }

客戶端程式tcp_client.c:

#include <stdio.h>  #include <winsock2.h>    #define BUF_LEN  100    int main(void)  {      WSADATA wd;      SOCKET ClientSock;      char Buf[BUF_LEN] = {0};      SOCKADDR_IN  ServerSockAddr;        /* 初始化操作sock需要的DLL */      WSAStartup(MAKEWORD(2,2),&wd);        /* 向伺服器發起請求 */      memset(&ServerSockAddr, 0, sizeof(ServerSockAddr));      ServerSockAddr.sin_family = AF_INET;      ServerSockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");      ServerSockAddr.sin_port = htons(1314);        while (1)      {          /* 創建客戶端socket */          if (-1 == (ClientSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)))          {              printf("socket error!n");              exit(1);          }          if (-1 == connect(ClientSock, (SOCKADDR*)&ServerSockAddr, sizeof(SOCKADDR)))          {              printf("connect error!n");              exit(1);          }          printf("請輸入一個字元串,發送給服務端:");          gets(Buf);          /* 發送數據到服務端 */          send(ClientSock, Buf, strlen(Buf), 0);            /* 接受服務端的返回數據 */          recv(ClientSock, Buf, BUF_LEN, 0);          printf("服務端發送過來的數據為:%sn", Buf);            memset(Buf, 0, BUF_LEN);   // 重置緩衝區          closesocket(ClientSock);   // 關閉套接字      }        // WSACleanup();  /*如果有退出循環的條件,這裡還需要清除對socket庫的使用*/      return 0;  }

我們上邊的IP地址概念那一部分中,有強調127.0.0.1這個IP是一個特殊的IP地址,這是回送地址,即本地機,一般用來測試使用。這個例子中我們就用到了。此外,埠我們設置為1314,這是隨意設置的,只要範圍在1024~65536之間就可以。

本文使用的是gcc編譯器編譯(關於gcc編譯器的相關介紹可查看往期筆記:【C語言筆記】使用notepad++、MinGW來開發C程式及【C語言筆記】windows命令行下編譯C程式),編譯命令如下:

gcc tcp_client.c -o tcp_client.exe -lwsock32  gcc tcp_server.c -o tcp_server.exe -lwsock32

這裡必須要加-lwsock32這個參數用於鏈接windows下socket編程必須的winsock2這個庫。若是使用集成開發環境,則需要把wsock32.lib放在工程目錄下,並在我們程式碼中#include <winsock2.h> 下面加上一行 #pragma comment(lib, "ws2_32.lib")程式碼(這種情況本人未驗證,有興趣的朋友可嘗試)。

實驗現象:

先啟動服務端程式tcp_server.exe,再啟動客戶端程式tcp_client.exe,並在客戶端中輸入字元串,則當服務端會接收到字元串時會列印輸出,與此同時也會往客戶端返回相同的數據:

動圖:

(2)Linux的程式:

在linux下,「一切都是文件」,所以這裡我們的套接字也當做文件來看待。

服務端程式linux_tcp_server.c:

#include <stdio.h>  #include <string.h>  #include <stdlib.h>  #include <unistd.h>  #include <arpa/inet.h>  #include <sys/socket.h>  #include <netinet/in.h>    #define BUF_LEN  100    int main(void)  {      int ServerFd, ClientFd;      char Buf[BUF_LEN] = {0};      struct sockaddr ClientAddr;      int addr_len = 0, recv_len = 0;      struct sockaddr_in ServerSockAddr;      int optval = 1;        /* 創建服務端文件描述符 */      if (-1 == (ServerFd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)))      {          printf("socket error!n");          exit(1);      }        /* 設置服務端資訊 */        memset(&ServerSockAddr, 0, sizeof(ServerSockAddr));     // 給結構體ServerSockAddr清零      ServerSockAddr.sin_family = AF_INET;                      // 使用IPv4地址      ServerSockAddr.sin_addr.s_addr = htonl(INADDR_ANY);        // 自動獲取IP地址      ServerSockAddr.sin_port = htons(6666);                  // 埠          // 設置地址和埠號可以重複使用      if (setsockopt(ServerFd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < 0)      {          printf("setsockopt error!n");          exit(1);      }        /* 綁定操作,綁定前加上上面的socket屬性可重複使用地址 */      if (-1 == bind(ServerFd, (struct sockaddr*)&ServerSockAddr, sizeof(struct sockaddr)))      {          printf("bind error!n");          exit(1);      }        /* 進入監聽狀態 */      if (-1 == (listen(ServerFd, 10)))      {          printf("listen error!n");          exit(1);      }        addr_len = sizeof(struct sockaddr);        while (1)      {          /* 監聽客戶端請求,accept函數返回一個新的套接字,發送和接收都是用這個套接字 */          if (-1 == (ClientFd = accept(ServerFd, (struct sockaddr*)&ClientAddr, &addr_len)))          {              printf("accept error!n");              exit(1);          }            /* 接受客戶端的返回數據 */          if ((recv_len = recv(ClientFd, Buf, BUF_LEN, 0)) < 0)          {              printf("recv error!n");              exit(1);          }            printf("客戶端發送過來的數據為:%sn", Buf);            /* 發送數據到客戶端 */          send(ClientFd, Buf, recv_len, 0);            /* 關閉客戶端套接字 */          close(ClientFd);            /* 清空緩衝區 */          memset(Buf, 0, BUF_LEN);      }        return 0;  }

客戶端程式linux_tcp_client.c:

#include <stdio.h>  #include <string.h>  #include <stdlib.h>  #include <unistd.h>  #include <arpa/inet.h>  #include <sys/socket.h>    #define BUF_LEN  100    int main(void)  {      int ClientFd;      char Buf[BUF_LEN] = {0};      struct sockaddr_in  ServerSockAddr;          /* 向伺服器發起請求 */      memset(&ServerSockAddr, 0, sizeof(ServerSockAddr));      ServerSockAddr.sin_family = AF_INET;      ServerSockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");      ServerSockAddr.sin_port = htons(6666);        while (1)      {          /* 創建客戶端socket */          if (-1 == (ClientFd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)))          {              printf("socket error!n");              exit(1);          }            /* 連接 */          if (-1 == connect(ClientFd, (struct sockaddr*)&ServerSockAddr, sizeof(ServerSockAddr)))          {              printf("connect error!n");              exit(1);          }            printf("請輸入一個字元串,發送給服務端:");          gets(Buf);          /* 發送數據到服務端 */          send(ClientFd, Buf, strlen(Buf), 0);          memset(Buf, 0, BUF_LEN);   // 重置緩衝區            /* 接受服務端的返回數據 */          recv(ClientFd, Buf, BUF_LEN, 0);          printf("服務端發送過來的數據為:%sn", Buf);            memset(Buf, 0, BUF_LEN);   // 重置緩衝區          close(ClientFd);   // 關閉套接字      }        return 0;  }

Linux下編譯就不需要添加-lwsock32參數:

gcc linux_tcp_server.c -o linux_tcp_server  gcc linux_tcp_client.c -o linux_tcp_client

實驗現象:

在調試這份程式時,出現了綁定錯誤:

經上網查詢發現是埠重複使用,可以在調用bind()函數之前調用setsockopt()函數以解決埠重複使用的問題:

2、基於UDP的本地客戶端、服務端資訊交互實例

(1)windows的程式

服務端程式udp_server.c:

#include <stdio.h>  #include <winsock2.h>    #define BUF_LEN  100    int main(void)  {      WSADATA wd;      SOCKET ServerSock;      char Buf[BUF_LEN] = {0};      SOCKADDR ClientAddr;      SOCKADDR_IN ServerSockAddr;      int addr_size = 0;          /* 初始化操作sock需要的DLL */      WSAStartup(MAKEWORD(2,2),&wd);        /* 創建服務端socket */      if(-1 == (ServerSock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)))      {          printf("socket error!n");          exit(1);      }        /* 設置服務端資訊 */      memset(&ServerSockAddr, 0, sizeof(ServerSockAddr));     // 給結構體ServerSockAddr清零      ServerSockAddr.sin_family = AF_INET;                      // 使用IPv4地址      ServerSockAddr.sin_addr.s_addr = htonl(INADDR_ANY);     // 自動獲取IP地址      ServerSockAddr.sin_port = htons(1314);                  // 埠        /* 綁定套接字 */        if (-1 == (bind(ServerSock, (SOCKADDR*)&ServerSockAddr, sizeof(SOCKADDR))))      {          printf("bind error!n");          exit(1);      }        addr_size = sizeof(SOCKADDR);        while (1)      {          /* 接受客戶端的返回數據 */          int str_len = recvfrom(ServerSock, Buf, BUF_LEN, 0, &ClientAddr, &addr_size);            printf("客戶端發送過來的數據為:%sn", Buf);            /* 發送數據到客戶端 */          sendto(ServerSock, Buf, str_len, 0, &ClientAddr, addr_size);            /* 清空緩衝區 */          memset(Buf, 0, BUF_LEN);      }        /*如果有退出循環的條件,這裡還需要清除對socket庫的使用*/      /* 關閉服務端套接字 */      //closesocket(ServerSock);      /* WSACleanup();*/        return 0;  }

客戶端程式udp_client.c:

#include <stdio.h>  #include <winsock2.h>    #define BUF_LEN  100    int main(void)  {      WSADATA wd;      SOCKET ClientSock;      char Buf[BUF_LEN] = {0};      SOCKADDR ServerAddr;      SOCKADDR_IN  ServerSockAddr;      int ServerAddrLen = 0;        /* 初始化操作sock需要的DLL */      WSAStartup(MAKEWORD(2,2),&wd);        /* 創建客戶端socket */      if (-1 == (ClientSock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)))      {          printf("socket error!n");          exit(1);      }        /* 向伺服器發起請求 */      memset(&ServerSockAddr, 0, sizeof(ServerSockAddr));      ServerSockAddr.sin_family = PF_INET;      ServerSockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");      ServerSockAddr.sin_port = htons(1314);        ServerAddrLen = sizeof(ServerAddr);        while (1)      {          printf("請輸入一個字元串,發送給服務端:");          gets(Buf);          /* 發送數據到服務端 */          sendto(ClientSock, Buf, strlen(Buf), 0, (struct sockaddr*)&ServerSockAddr, sizeof(ServerSockAddr));            /* 接受服務端的返回數據 */          recvfrom(ClientSock, Buf, BUF_LEN, 0, &ServerAddr, &ServerAddrLen);          printf("服務端發送過來的數據為:%sn", Buf);            memset(Buf, 0, BUF_LEN);   // 重置緩衝區      }        closesocket(ClientSock);   // 關閉套接字      // WSACleanup();  /*如果有退出循環的條件,這裡還需要清除對socket庫的使用*/      return 0;  }

(2)Linux下的程式

服務端程式linux_udp_server.c:

#include <stdio.h>  #include <string.h>  #include <stdlib.h>  #include <unistd.h>  #include <arpa/inet.h>  #include <sys/socket.h>  #include <netinet/in.h>    #define BUF_LEN  100    int main(void)  {      int ServerFd;      char Buf[BUF_LEN] = {0};      struct sockaddr ClientAddr;      struct sockaddr_in ServerSockAddr;      int addr_size = 0;      int optval = 1;        /* 創建服務端socket */      if ( -1 == (ServerFd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)))      {          printf("socket error!n");          exit(1);      }        /* 設置服務端資訊 */      memset(&ServerSockAddr, 0, sizeof(ServerSockAddr));     // 給結構體ServerSockAddr清零      ServerSockAddr.sin_family = AF_INET;                      // 使用IPv4地址      ServerSockAddr.sin_addr.s_addr = htonl(INADDR_ANY);     // 自動獲取IP地址      ServerSockAddr.sin_port = htons(1314);                  // 埠        // 設置地址和埠號可以重複使用      if (setsockopt(ServerFd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < 0)      {          printf("setsockopt error!n");          exit(1);      }        /* 綁定操作,綁定前加上上面的socket屬性可重複使用地址 */      if (-1 == bind(ServerFd, (struct sockaddr*)&ServerSockAddr, sizeof(ServerSockAddr)))      {          printf("bind error!n");          exit(1);      }        addr_size = sizeof(ClientAddr);        while (1)      {          /* 接受客戶端的返回數據 */          int str_len = recvfrom(ServerFd, Buf, BUF_LEN, 0, &ClientAddr, &addr_size);            printf("客戶端發送過來的數據為:%sn", Buf);            /* 發送數據到客戶端 */          sendto(ServerFd, Buf, str_len, 0, &ClientAddr, addr_size);            /* 清空緩衝區 */          memset(Buf, 0, BUF_LEN);      }        close(ServerFd);        return 0;  }

客戶端程式linux_udp_client.c:

#include <stdio.h>  #include <string.h>  #include <stdlib.h>  #include <unistd.h>  #include <arpa/inet.h>  #include <sys/socket.h>    #define BUF_LEN  100    int main(void)  {      int ClientFd;      char Buf[BUF_LEN] = {0};      struct sockaddr ServerAddr;      int addr_size = 0;      struct sockaddr_in  ServerSockAddr;        /* 創建客戶端socket */      if (-1 == (ClientFd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)))      {          printf("socket error!n");          exit(1);      }        /* 向伺服器發起請求 */      memset(&ServerSockAddr, 0, sizeof(ServerSockAddr));      ServerSockAddr.sin_family = PF_INET;      ServerSockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");      ServerSockAddr.sin_port = htons(1314);        addr_size = sizeof(ServerAddr);        while (1)      {          printf("請輸入一個字元串,發送給服務端:");          gets(Buf);          /* 發送數據到服務端 */          sendto(ClientFd, Buf, strlen(Buf), 0, (struct sockaddr*)&ServerSockAddr, sizeof(ServerSockAddr));            /* 接受服務端的返回數據 */          recvfrom(ClientFd, Buf, BUF_LEN, 0, &ServerAddr, &addr_size);          printf("服務端發送過來的數據為:%sn", Buf);            memset(Buf, 0, BUF_LEN);   // 重置緩衝區      }        close(ClientFd);   // 關閉套接字        return 0;  }

實驗現象:

實驗現象如實例1。

五、總結

本筆記簡單介紹了一些與socket編程相關的一些知識點:IP地址,什麼是埠,協議等。重點介紹了TCP、UDP通訊的一些原理及其API介面的用法,並給出了windows和linux下的TCP、UDP通訊實例。以上就是關於socket編程的一些總結,如有錯誤,歡迎指出!

Tags: