一、基礎概念
1、網絡架構
Client/Server結構(C/S結構)客戶機和服務器結構。本文的主角。B/S結構(Browser/Server,瀏覽器/服務器模式),WEB瀏覽器是客戶端最主要的應用軟件。
2、IP
IP地址是網路通信尋址的主要手段
3、端口(port )
每台計算機有很多個端口。通常是一個進程(運行着的程序)對應一個端口,訪問該主機的某個端口就是訪問對應的進程。有些端口是默認的對應一些進程,像其中80端口分配給WWW服務,21端口分配給FTP服務。通常我們選用1024以上的端口。
4、socket(套接字)
套接字是一個很抽象的概念。可以理解為連接兩個端口之間一條虛擬的線,而端口就是虛擬的接口,或者可以理解為電源線跟插頭的關系。要進行網絡通信,少了它不行。
二、c/s架構
s是服務器(運行了服務端程序的計算機),c是客戶機。c向s發送消息(請求),s接受到消息並處理(響應)。建立連接后s也可以主動向c發送消息,通常是多個c對應一個s。這里我用的只有一個客戶端。
三、tcp通信
1、連接通信
在進行通信前,需要做一個虛連接。即客戶端c想要與服務端s進行通信必須先要與之進行連接。
2、客戶端
客戶端通信很簡單,只要取得與服務端的聯系就可以進行信息的收發。
3、服務端
服務端是一個進程,總是等待着客戶端發來請求,並處理相應的請求。
四、通信過程
不涉及具體調用函數的說明。
1、客戶端設計
第一步、確定要進行連接的服務端的信息(IP、port)
第二步、獲取一個socket,調用相應的函數得到
第三步、用得到的socket與服務端的信息去調用connect與服務端進行連接
第四部、收發消息(send/recv)
第五步、關閉打開的套接字
2、服務端的設計
第一步、確定本機IP和要與程序綁定的端口
第二步、獲取一個監聽用的socket。
第三步、將得到的socket與確定后的端口綁定,調用bind.
第四步、監聽。坐等客戶端的到來(沒來是一個處於阻塞的函數,調用 listen)
第五步、接受客戶端的請求,並建立一個新的套接字來與客戶端通信(accept)
第六步、用建立的套接字與客戶端通信(send/recv)。
第七步、關閉已經打開的套接字,跟文件處理是一樣的。
五、實例演示
1、說明:
首先,程序運行在Windows下有環境依賴,要做跟Windows有關的初始化。包括一些頭文件的包括,需要鏈接的庫等。
其次是程序里面分別在客戶端和服務端創建了一個線程用來收信息,增加程序的體驗感。同樣是windows的原因,調用createThird創建線程時要特別注意線程函數的格式。
也可以選用其他創建線程的函數。
2、客戶端代碼
#include<stdio.h> #include <stdlib.h> #include<string.h> #include <winsock2.h> #include <windows.h> /*添加庫的方法:工程->設置->連接->對象/庫模塊 中加入ws2_32.lib*/ #pragma comment(lib,"ws2_32.lib") #define PORT 8888 #define ADDR "127.0.0.1" //函數聲明 DWORD WINAPI ThreadProc(LPVOID lpParam); //主函數 int main(){ SOCKET scoket; SOCKADDR_IN serAddr; int i=0; char buffer[1024]; int nRet=0; WSADATA wsock; //第一步,很重要。做環境初始化 if(WSAStartup(MAKEWORD(2,2),&wsock)!=0) { return 0; } //第二步,獲取套接字 if((scoket=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP))==INVALID_SOCKET) { WSACleanup();//獲取套接字失敗后,需要關閉已經初始化的環境 return 0; } //設置SOCKADDR_IN地址結構 serAddr.sin_family=AF_INET;//ipv4協議簇 serAddr.sin_port=htons(PORT);//設置端口 serAddr.sin_addr.s_addr=inet_addr(ADDR); //設置IP地址 //第三步,進行連接 if((connect(scoket,(SOCKADDR*)&serAddr,sizeof(serAddr)))==SOCKET_ERROR) { printf("error:%d",WSAGetLastError()); return 0; } //創建一個線程,用來接收數據。 //windows里面調用此函數,Linux不一定 CreateThread ( NULL, NULL, ThreadProc,&scoket,0, NULL); while(1) { memset(buffer,0,sizeof(buffer));//緩存清零 gets(buffer); if(strcmp("exit",buffer)==0) goto EXIT; if((nRet=send(scoket,buffer,strlen(buffer),0))==SOCKET_ERROR) { printf("error:%d",WSAGetLastError()); goto EXIT; } } //錯誤處理 EXIT: closesocket(scoket);/*關閉不使用的套接字,跟文件操作一樣,網絡也是稀缺資源。*/ WSACleanup(); //關閉已經初始化的環境 return 0; } //線程函數,注意返回值和參數類型是固定的 DWORD WINAPI ThreadProc(LPVOID lpParam) { SOCKET *sk=(SOCKET *)lpParam; char buffer[1024]; while(1) { memset(buffer,0,sizeof(buffer)); if(recv(*sk,buffer,sizeof(buffer),0)==SOCKET_ERROR) { printf("error:%d",WSAGetLastError()); closesocket(*sk); WSACleanup(); return 0; } if(strcmp(buffer,"exit")==0){ return 0; } puts(buffer); } }
3、服務端代碼
#include<stdio.h> #include<string.h> #include <stdlib.h> #include <winsock2.h> #include<windows.h> /*添加庫的方法:工程->設置->連接->對象/庫模塊 中加入ws2_32.lib*/ #pragma comment(lib,"WS2_32.lib") #define PORT 8888 #define ADDR "127.0.0.1" DWORD WINAPI ThreadProc(LPVOID lpParam); int main(int argc,char* argv[]){ WSADATA wsock; SOCKET listensocket,newconnection; SOCKADDR_IN serAddr,cliAddr; int cliAddrLen=sizeof(cliAddr); int nRet=0; char buffer[1024]; //第一步,很重要。做環境初始化 if(WSAStartup(MAKEWORD(2,2),&wsock)!=0) { return 0; } //第二步,獲取套接字 if((listensocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET){ printf("error:%d",WSAGetLastError()); WSACleanup();//關閉已經初始化的環境 return 0; } printf("設置監聽套接字\n"); //設置SOCKADDR_IN地址結構 serAddr.sin_family = AF_INET; serAddr.sin_port = htons(PORT); serAddr.sin_addr.s_addr=inet_addr(ADDR); //serAddr.sin_addr.S_un.S_addr = INADDR_ANY; printf("綁定\n"); //綁定套接字到相應的端口 if(bind(listensocket, (SOCKADDR *)&serAddr,sizeof(serAddr))== SOCKET_ERROR){ printf("error:%d",WSAGetLastError()); goto ERR; } printf("進入監聽……\n"); //監聽等待,無連接時處於阻塞狀態 if(listen(listensocket, 5) == SOCKET_ERROR) { printf("error:%d",WSAGetLastError()); goto ERR; } //錯誤處理 goto NEXT; ERR: closesocket(listensocket); WSACleanup(); return 0; NEXT: printf("設置接收連接套接字\n"); //引用新的套接字與客戶端進行通信 if((newconnection = accept(listensocket, (SOCKADDR *) &cliAddr, &cliAddrLen)) == INVALID_SOCKET){ printf("error:%d",WSAGetLastError()); goto ERR; } //關閉監聽套接字。也可以循環監聽,進行多並發處理。不關閉 closesocket(listensocket); printf("收發數據……"); //創建一個線程,用來接收數據。 //windows里面調用此函數,Linux不一定 CreateThread ( NULL, NULL, ThreadProc,&newconnection,0, NULL); //循環發送數據 while(1) { memset(buffer,0,sizeof(buffer));//清零 gets(buffer); if(strcmp(buffer,"exit")==0){ goto EXIT; } if((nRet=send(newconnection,buffer,strlen(buffer),0))==SOCKET_ERROR){ printf("error:%d",WSAGetLastError()); goto EXIT; } } EXIT: closesocket(newconnection); WSACleanup(); return 0; } //線程函數,注意返回值和參數類型是固定的 DWORD WINAPI ThreadProc(LPVOID lpParam) { SOCKET *sk=(SOCKET *)lpParam; char buffer[1024]; while(1) { memset(buffer,0,sizeof(buffer));//清零 if(recv(*sk,buffer,sizeof(buffer),0)==SOCKET_ERROR) { printf("error:%d",WSAGetLastError()); closesocket(*sk); WSACleanup(); return 0; } if(strcmp(buffer,"exit")==0){ return 0; } puts(buffer); } }
4、運行結果

