基於TCP(面向鏈接)的socket編程
一.基於TCP(面向鏈接)的socker編程的服務器端程序流程以下:
(1) 建立套接字(socket)
(2) 將套接字綁定到一個本地地址和端口上(bind)
(3) 將套接字設置爲監聽模式,準備接收客戶請求(listen)
(4) 等待客戶請求的到來;當客戶請求到來後,接受鏈接請求,返回一個新的對應於這次鏈接的套接字(accept)
(5) 用返回的套接字和客戶端進行通訊(send/recv)
(6) 返回等待另外一個客戶請求
(7) 關閉套接字
二. 基於TCP(面向鏈接)的socket編程的客戶端程序流程以下:
(1)建立套接字(socket)
(2)向服務器發出鏈接請求(connect)
(3)和服務器端進行通訊(send/recv)
(4)關閉套接字
例子:
服務器端:
- //服務器端
- #include <WINSOCK2.H>
- #include <stdio.h>
- void main()
- {
- /******************************************************** 在利用套接字編程時,首先要加載套接字,經過WSAStartup函數來實現.
- 函數原型:int WSAStartup(WORD wVersionRequested,LPWSADATA lpWSAData);
- (1) wVersionRequested:用來指定準備加載的Winsock庫的版本,高位字節指定所需的winsock庫的副版本,低位字節則是主版本。能夠利用MAKEWORD(x, y)宏來得到wVersionRequested的正確值。
- (2) lpWSAData是指向WSADATA結構的指針,WSAStartup函數用其加載的庫版本有關的信息填充在這個結構中
- ***********************************************/
- WORD wVersionRequested;
- WSADATA wsaData;
- int err;
- wVersionRequested = MAKEWORD(1, 1);
- err = WSAStartup(wVersionRequested, &wsaData);
- if(err != 0)
- {
- //加載失敗
- return;
- }
- if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)
- {
- //釋放爲該應用程序分配的資源,終止對WinSock動態庫的使用
- WSACleanup();
- return;
- }
- /*************************************************************************************/
- /* 加載套接字庫以後,調用socket函數建立套接字
- 函數原型:SOCKET socket(int af, int type, int protocol);
- (1):af參數指定地址族,對於TCP/IP協議的套接字,它只能是AF_INET(也能夠寫成PF_INET)
- (2):type參數,指定socket類型,對於1.1版本的socket,只支持兩種:SOCK_STREAM, SOCK_DGRAM
- (3):protocol參數是與特定的地址家族相關的協議,推薦指定爲0
- 返回值:若是socket函數調用成功,返回一個新的socket數據類型的套接字描述符,若是調用失敗,
- 此函數返回一個INVALID_SOCKET值,錯誤信息經過WSAGetLastError函數返回
- */
- /************************************************************************************/
- //建立用於監聽的套接字
- SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, 0);
- /**************************************************************************************************************/
- /* 經過bind函數將套接字綁定到本地的某個地址和端口上
- 函數原型:int bind(SOCKET s, const struct sockaddr FAR *name, int namelen);
- (1)s參數指定要綁定的套接字。
- (2)name參數指定了該套接字的本地地址信息,此爲一個指向sockaddr結構的指針變量
- (3)namelen參數指定該地址結構的長度
- 備註1:struct sockaddr{
- u_short sa_family; //指定地址家族,對TCP/IP協議的套接字,必須爲AF_INET
- char sa_data[14]; //僅表示要求一塊內存分配區,起到佔位的做用,該區域中指定與協議相關的具體地址信息
- };
- 在基於TCP/IP的socket編程中,使用sockaddr_in結構替換sockaddr
- struct sockaddr_in{
- short sin_family; //指定地址家族,對TCP/IP協議的套接字,必須爲AF_INET
- unsigned short sin_port; //指定分配給套接字的端口,必須爲網絡字節序
- struct in_addr sin_addr; //指定套接字的主機IP地址,必須爲網絡字節序
- char sin_zero[8];//起佔位做用
- };
- 其中sin_addr成員的類型是in_addr,該結構定義以下:
- struct in_addr{
- union {
- struct { u_char s_b1,s_b2,s_b3,s_b4; } S_un_b; //Address of the host formatted as four u_chars.
- struct { u_short s_w1,s_w2; } S_un_w; //Address of the host formatted as two u_shorts.
- u_long S_addr; //Address of the host formatted as a u_long.
- } S_un;
- };
- 備註2:(1) u_short htons(u_short hostshort );
- 此函數用於將一個u_short類型的值從主機字節順序轉換爲TCP/IP網絡字節順序。
- (2) u_long htonl(u_long hostlong );
- 此函數用於將一個u_long類型的值從主機本身順序轉換爲TCP/IP網絡字節順序。
- (3) unsigned long inet_addr(const char FAR *cp);
- 此函數須要一個字符串做爲其參數,該字符串指定了以點分十進制格式表示的IP地址,返回一個適合分配給S_addr的u_long類型的數值
- (4) char FAR * inet_ntoa(struct in_addr in);
- 此函數接受一個in_addr結構體類型的參數並返回一個以點分十進制格式表示的IP地址字符串
- */
- /******************************************************************************************************************/
- SOCKADDR_IN addrSrv;
- addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);//將u_short類型的值從主機字節序轉換位網絡字節序
- addrSrv.sin_family = AF_INET;
- addrSrv.sin_port = htons(6001); //將u_long類型的值從主機字節序轉換爲網絡字節序
- //綁定套接字
- bind(sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));
- /************************************************************************/
- /* 將指定的套接字設置爲監聽模式
- 函數原型:int listen(SOCKET s, int backlog);
- (1): s參數是套接字描述符
- (2): backlog參數是等待鏈接隊列的最大長度
- */
- /************************************************************************/
- //將套接字設爲監聽模式,準備接收客戶請求
- listen(sockSrv, 5);
- SOCKADDR_IN addrClient;
- int len = sizeof(SOCKADDR);
- while (1)
- {
- /************************************************************************/
- /* 利用accept函數接受客戶端發送的請求
- 函數原型:SOCKET accept(SOCKET s, struct sockaddr FAR *addr, int FAR *addrlen);
- (1) s參數是套接字描述符,該套接字已經經過listen函數將其設置爲監聽狀態
- (2) addr參數指向一個緩衝區的指針,該緩衝區用來接收鏈接實體的地址,即客戶端的IP地址和端口
- (3) addrlen參數指向一個整型的指針,返回包含地址信息的長度
- */
- /************************************************************************/
- //等待客戶請求
- SOCKET sockConn = accept(sockSrv, (SOCKADDR*)&addrClient, &len);
- char sendBuf[100];
- sprintf(sendBuf,"Welcome %s to xidian", inet_ntoa(addrClient.sin_addr));
- printf("Welcome %s to xidian\n", inet_ntoa(addrClient.sin_addr));
- //發送數據
- send(sockConn, sendBuf, strlen(sendBuf)+1, 0);
- char recvBuf[100]={'0'};
- //接收數據
- recv(sockConn, recvBuf, 100, 0);
- //打印接收的數據
- printf("%s\n", recvBuf);
- //關閉套接字
- closesocket(sockConn);
- }
- }
客戶端:
- //TCP客戶端
- #include <WINSOCK2.H>
- #include <stdio.h>
- void main()
- {
- //加載套接字庫
- WORD wVersionRequested;
- WSADATA wsaData;
- int err;
- wVersionRequested = MAKEWORD(1, 1);
- err = WSAStartup(wVersionRequested, &wsaData);
- if (err != 0)
- {
- return;
- }
- if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)
- {
- WSACleanup();
- return;
- }
- //建立套接字
- SOCKET sockClient = socket(AF_INET, SOCK_STREAM, 0);
- //要鏈接服務器的信息,注意使用的是網絡字節序
- SOCKADDR_IN addrSrv;
- addrSrv.sin_family = AF_INET;
- addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
- addrSrv.sin_port = htons(6001);
- //向服務器發送鏈接請求
- connect(sockClient, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));
- //接收數據
- char recvBuf[100]={'0'};
- recv(sockClient, recvBuf, 100, 0);
- printf("%s\n", recvBuf);
- //發送數據
- send(sockClient, "This is a client test", strlen("This is a client test")+1, 0);
- //關閉套接字
- closesocket(sockClient);
- WSACleanup();
- }