Socket編程基礎(一)

來源:互聯網
上載者:User

在Windows環境下,套介面的通訊方式分為兩種:阻塞方式和非阻塞方式。阻塞方式下工作的套介面在進行I/O操作時,函數要等待到相關操作完成

後才能返回(或者可以使用WSACancelBlockingCall( )調用喚起一個阻塞操作)。

本篇部落格實現windows下的阻塞IO socket編程。

簡單TCP阻塞模式:用戶端的代碼:
const char HOST_ADDRESS[] = "127.0.0.1";const int HOST_PORT = 8888;const int DEFAULT_BUFFER = 1<<12; //4kvoid testClient(){WSADATA wsaData;//typedef unsigned short WORD;16位無符號整形WORD version = MAKEWORD(2,2);//0x0202int err = WSAStartup(version,&wsaData);//cout<<(int)HIBYTE(version)<<(LOBYTE(version)==2);if(0 != err){cerr<<"Socket2.2 初始化失敗!,Exit";exit(0);}SOCKET sockClient = socket(AF_INET,SOCK_STREAM,0); if(INVALID_SOCKET == sockClient){cerr<<"Socket建立失敗!Exit";WSACleanup();exit(0);}//設定發送地址sockaddr_in addrto;memset(&addrto,0,sizeof(addrto));addrto.sin_family = AF_INET;//tcp/ip系的協議addrto.sin_addr.s_addr = inet_addr(HOST_ADDRESS);addrto.sin_port = htons(HOST_PORT);if(connect(sockClient,(SOCKADDR*) &addrto,sizeof(addrto)) ==SOCKET_ERROR){cerr<<"Failed to connect.\n";WSACleanup();exit(0);}//發送和接受資料int bytesSend,left,idx = 0;int bytesRecv = SOCKET_ERROR;char sendbuf[DEFAULT_BUFFER] = "";char recvbuf[DEFAULT_BUFFER] = "";while(true){cout<<"[發送資料到伺服器]:";cin>>sendbuf;//cout<<sendbuf;left = strlen(sendbuf);//分多次發送資料while(left>0){bytesSend = send(sockClient,&sendbuf[idx],left,0);if(bytesSend == 0)exit(-1);if(bytesSend == SOCKET_ERROR){cout<<"Send Failed:"<<WSAGetLastError()<<endl;exit(-1);}left -= bytesSend;idx += bytesSend;}idx = 0;while(bytesRecv == SOCKET_ERROR){bytesRecv = recv(sockClient,recvbuf,DEFAULT_BUFFER,0);if(bytesRecv == 0 || bytesRecv == WSAECONNRESET){//遠程停止了虛電路cout<<"串連已關閉!"<<endl;WSACleanup();closesocket(sockClient);exit(-1);}if(bytesRecv<0) {cout<<"未收到回複"<<endl;exit(-1);}cout<<"[從伺服器接收的資料]:"<<recvbuf<<endl;}bytesRecv = SOCKET_ERROR;memset(sendbuf,0,DEFAULT_BUFFER);memset(recvbuf,0,DEFAULT_BUFFER);}}

伺服器端的代碼:

void StartTCPServer(){WSADATA wsa;WORD version = MAKEWORD(2,2);int ret;ret = WSAStartup(version,&wsa);if(ret != NO_ERROR){cout<<"初始化socket失敗!"<<endl;return;}SOCKET listenSocket;//監聽的socketSOCKET clientSocket;//用戶端d const char* adr = "127.0.0.1";const unsigned port = 8088;listenSocket = socket(AF_INET,SOCK_STREAM,0);SOCKADDR_IN serverAdrr;serverAdrr.sin_family = AF_INET;serverAdrr.sin_port = htons(port);//serverAdrr.sin_addr.S_un.S_addr = inet_addr(adr);serverAdrr.sin_addr.s_addr = INADDR_ANY;//綁定伺服器的socket與伺服器位址ret = bind(listenSocket,(LPSOCKADDR)&serverAdrr,sizeof(serverAdrr));if(ret == SOCKET_ERROR){cout<<"綁定socket失敗:"<<WSAGetLastError()<<endl;return;}ret = listen(listenSocket,2);if(ret == SOCKET_ERROR){cout<<"監聽連接埠失敗:"<<WSAGetLastError()<<endl;return;}clientSocket = accept(listenSocket,NULL,NULL);if(ret == SOCKET_ERROR){cout<<"監聽連接埠失敗:"<<WSAGetLastError()<<endl;return;}const unsigned size = 1<<8;char buffer[size];//buffer = new char;memset(buffer,0,sizeof(buffer));const char * str = "串連伺服器成功...";strcpy_s(&buffer[0],sizeof(buffer),str);ret = send(clientSocket,buffer,sizeof(buffer),0);cout<<"已發送的位元組數:"<<ret<<endl;do{memset(buffer,0,sizeof(buffer));ret = recv(clientSocket,buffer,size,0);if(ret > 1){cout<<ret<<endl;cout<<"用戶端回複:"<<buffer<<endl;}}while(true);}
上面例子實現了windows下阻塞模式TCP流式通訊端編程。基本流程如下:


伺服器端的具體步驟:1.初始化winsock庫在調用Socket前先要初始化,即載入相應版本的DLL,通過調用WSAStartup函數,將載入成功的Socket庫版本的相關資訊填在LPWSADATA結構中;

WSADATA lpWSAData;    WSAStartup(MAKEWORD(2,2),&lpWSAData);
2.建立Socket:完成初始化之後,調用socket函數建立一個通訊端,返回通訊端控制代碼,在其後通訊中始終用來標識通訊端,若調用失敗則返回INVALID_SOCKET;3.綁定Socket綁定服務端地址:在為某種特定協議建立了通訊端後,就用bind函數將通訊端綁定到一個本機地址,其類型是sockaddr,用於指明通訊端綁定地址,包括IP地址與連接埠號碼;
bind(sktConnect,(struct sockaddr far *)&sockaddrin,sizeof(sockaddrin));

4.服務端監聽網路:socket利用listen函數設定狀態位,用來檢測是否有到來的串連請求,然後調用accept函數,準備接收用戶端串連訊號,無串連請求時,服務進程被阻塞;

listen(sktConnect,1);    sktClient=accept(sktConnect,(struct sockaddr far *)&sockaddrin,& sockaddrlen);
5.收發資料:一旦用戶端通訊端接收到來自服務端的接受訊號,則表示雙方已經實現串連,任何一方均可使用Send/Write函數和Recv/Read函數向對方發送或者接收資料;
send(sktClient,chrSend,10,0);recv(sktClient,chrReceive,10,0);
用戶端的具體步驟:

1.初始化與建立用戶端Winsock:

首先WSAStartup函數來填充WSADATA結構,隨後同樣調用socket函數建立用戶端的通訊端,給用戶端Sockaddr_in結構賦值,地址類型和連接埠號碼與服務端相同

2.通訊端選項設定:

使用setsockopt函數設定通訊端選項,比如發送或者接收的逾時設定,緩衝區的容量設定,使用ioctlsocket函數設定socket的I/O模式等;

int ret=ioctlsocket(sktClient,FIONBIO,(unsigned long*)&ul);
3.雙方建立串連:
connect(sktClient,(const struct sockaddr *)&sockaddrin,sizeof (sockaddrin));

用戶端調用connect函數向服務端發出串連請求,當串連請求到來時,被阻塞服務端的accept函數產生一個新的位元組流通訊端,返回用戶端Sockaddr_in結構變數,用帶有用戶端地址的通訊端與用戶端進行串連,然後向用戶端返回接收訊號;

4.收發資料

5.關閉通訊端與winsock登出:

服務端和用戶端可以通過調用closesocket函數關閉通訊端上的所有發送和接收操作,撤銷通訊端並且中斷串連。同時,winsock服務的動態連結程式庫在使用結束後,應用程式必須調用WSACleanup函數將其登出,並釋放分配的資源。


下面來看一些基本的概念:1.通訊端(socket)

它是網路的基本構件。它是可以被命名和定址的通訊端點,使用中的每一個通訊端都有其類型和一個與之相連聽進程。通訊端存在通訊地區(通訊地區又稱地址簇)中。通訊端只與同一地區中的通訊端交換資料(跨地區時,需要執行某和轉換進程才能實現)。WINDOWS 中的通訊端只支援單一通訊域。

通訊端是一個int類型整數,類似於檔案描述符或者HANDLE。

通訊端分為流通訊端(SOCK_STREAM)和資料通訊端(SOCK_DGRAM)。流通訊端提供雙向有序無重複且無記錄邊界的資料流服務,它適用於處理大量資料。是連線導向的,通訊雙方進行資料交換之前,必須建立一條路徑。若使用TCP協議則應把通訊端設定為流式的。

資料通訊端支援雙向的資料流,但並不保證傳輸的可靠性有序性和無重複性,是不需連線的。適用於UDP協議。

2.通訊端地址(sockaddr與sockaddr_IN)

struct sockaddr結構如下:
struct sockaddr{  u_short  sa_family;  char     sa_data[14];};

sa_family是socket address family的縮寫,表示地址種類,一般用AF_INET表示該socket處於internet域。另外的14位元組是用來描述地址的。這是一種通用結構,事實上,當我們指定sa_family=AF_INET之後,sa_data的形式也就被固定了下來:最前端的2位元組用於記錄16位的連接埠,緊接著的4位元組用於記錄32位的IP地址,最後的8位元組清空為零。該地址結構隨所選擇的協議的不同而變化,因此一般情況下另一個與該地址結構大小相同的sockaddr_in結構更為常用,sockaddr_in結構用來標識TCP/IP協議下的地址,可強制將sockaddr_in轉換為sockaddr。sockaddr_in的結構格式如下:

struct sockaddr_in{  short        sin_family;   unsigned short    sin_port;//連接埠  struct  in_addr  sin_addr;  char         sin_zero[8];};
in_addr顯然是internet address,其結構如下:
struct in_addr{    unsigned long s_addr;};

s_addr是ip地址,可調用inet_addr函數把類似“127.0.0.1”的字串轉換成long。3.Winsock的啟動和終止Winsock的服務是以動態連結程式庫Winsock DLL形式實現的,所以必須先調用WSAStartup函數對Winsock DLL進行初始化,設定版本號碼,並分配必要資源。WSAStartup函數原型如下:

int WSAStartup(WORD wVersionRequested,LPWSADATA lpWSAData);其中wVersionRequested用於指定Winsock庫的版本,通常高位位元組指定副版本,低位位元組則是主要版本。然後用宏MAKEWORD(X,Y)(X為高位位元組,Y是低位位元組)擷取wVersionRequested的正確值。lpWSAData參數是指向LPSWSADATA結構的指標,該結構包含了載入庫版本有關的資訊,它的格式如下:
typedef struct WSAData{  WORD      wVersion;  //Winsock版本  WORD      wHightVersion;  char       szDescription[WSADESCRIPTION_LEN+1];  char       szSystemStatus[WSASYS_STATUS_LEN+1];  unsigned short   iMaxSocket;  //開啟的通訊端的最大長度  unsigned short   iMaxUdpDg;  //開啟的資料報的最大長度  char FAR*    lpVendorInfo;}WSADATA,*LPWSADATA;
下面來實現udp協議收發訊息:udp.h
#pragma once#include <WinSock2.h>#include <tchar.h>#include <process.h>#include <string>#include <iostream>#pragma comment(lib,"Ws2_32.lib")using namespace std;//-----------------------------------------------//udp用戶端//-----------------------------------------------const char* DEFAULT_HOST = "127.0.0.1";const int DEFAULT_PORT = 8088;struct sockfd{int fd; SOCKADDR* addr;sockfd():fd(),addr(NULL){}~sockfd(){addr = NULL;}};int InitSocket(){WSADATA wsa;if (WSAStartup(0x202,&wsa)!= NO_ERROR) {cerr<<"初始化Socket錯誤"<<endl;return -1;}return 0;}//-----------------------------------------------//迴圈發送所有的buffer//-----------------------------------------------int sendAll(sockfd* sc,const char* buffer,size_t sz){int err = 0;int sen = 0;while (true) {err = sendto(sc->fd,buffer,sz - sen,0,sc->addr,sizeof SOCKADDR_IN);if (err <= 0) {return err;}sen += err;if (sen == sz) {cout<<"發送訊息:"<<buffer<<" 共"<<sz<<"位元組"<<endl;break;}}return sen;}int readAll(sockfd* sc,char* buffer,size_t sz){int err = 0;int len = sizeof SOCKADDR_IN;while (true) {err = recvfrom(sc->fd,buffer,sz,0,sc->addr,&len);if (err <= 0) {//cerr<<""<<endl;return err;}break;}return err;}

用戶端類:

#pragma once#include "../Common/UDP.h"//發送訊息的線程unsigned WINAPI SendTh(LPVOID lparam){sockfd* sc = reinterpret_cast<sockfd*>(lparam); unsigned char head[2];char buffer[1024];while (!cin.bad()){memset(buffer,0,sizeof buffer);cin>>buffer;//先發送訊息頭部size_t len = strlen(buffer);//訊息的實際長度unsigned char head[2];//2Byte最大可以表示(1<<16)-1head[0] = (len>>8) & 0xff; //低位head[1] = len & 0xff;//高位int r = sendAll(sc,(char*)head,2);if (r < 0) {cerr<<"發送頭部失敗"<<endl;return -1;}//再發送訊息主體r = sendAll(sc,buffer,len);if (r < 0) {cerr<<"發送訊息失敗"<<endl;return -1;}}return 0;}//接受訊息的線程unsigned WINAPI RecvTh(LPVOID lparam){sockfd* sc = reinterpret_cast<sockfd*>(lparam);while (true) {unsigned char head[2];memset(head,0,2);if (readAll(sc,(char*)head,2) == 0) {break;}size_t len = (head[0]<<8) | head[1];if (len > 0) {//接收訊息char* temp = new char[len+1];memset(temp,0,len+1);readAll(sc,temp,len);temp[len] = '\0';cout<<"從伺服器端收到訊息:"<<temp<<" 共:"<<len<<"位元組"<<endl;delete temp;}}return 0;}int _tmain(int args,TCHAR* argv[]){InitSocket();SOCKET clientSock = socket(AF_INET,SOCK_DGRAM,0);SOCKADDR_IN addr;memset(&addr,0,sizeof addr);addr.sin_family = AF_INET;addr.sin_addr.s_addr = inet_addr(DEFAULT_HOST);addr.sin_port = htons(DEFAULT_PORT);sockfd* sc = new sockfd;sc->fd = clientSock;sc->addr = reinterpret_cast<LPSOCKADDR>(&addr);cout<<"------------------UDP用戶端已啟動-----------------"<<endl;//開啟線程unsigned thid;HANDLE send = (HANDLE)_beginthreadex(NULL,0,SendTh,sc,0,&thid);HANDLE recive = (HANDLE)_beginthreadex(NULL,0,RecvTh,sc,0,&thid);WaitForSingleObject(send,INFINITE);WaitForSingleObject(recive,INFINITE);CloseHandle(send);CloseHandle(recive);delete sc;closesocket(clientSock);WSACleanup();system("pause");return 0;}

伺服器類:接收用戶端訊息並返回給用戶端

#pragma once#include "../Common/UDP.h"void SendMsg(sockfd* sc,const char* buffer){unsigned char head[2];memset(head,0,2);size_t len = strlen(buffer);head[0] = (len >> 8) & 0xff;head[1] = len & 0xff;int r = sendAll(sc,(char*)head,2);if (r < 0) {cerr<<"發送頭部失敗"<<endl;}r = sendAll(sc,buffer,len);if (r < 0) {cerr<<"發送訊息失敗"<<endl;}}void RecvMsg(sockfd* sc){unsigned char head[2];while (true){//接收訊息頭部memset(head,0,2);if (readAll(sc,(char*)head,2) == 0) {break;}size_t len = head[0]<<8 | head[1];char* msg = new char[len+1];readAll(sc,msg,len);msg[len] = '\0';cout<<"從用戶端收到訊息:"<<msg<<" 共"<<len<<"位元組"<<endl;//把訊息返回給用戶端SendMsg(sc,msg);delete msg;}}int _tmain(int argc,TCHAR* argv[]){InitSocket();SOCKET reciveSocket = socket(AF_INET,SOCK_DGRAM,0);SOCKADDR_IN serverAddr;serverAddr.sin_family = AF_INET;serverAddr.sin_addr.s_addr = inet_addr(DEFAULT_HOST);serverAddr.sin_port = htons(DEFAULT_PORT);int iResult = bind(reciveSocket,reinterpret_cast<LPSOCKADDR>(&serverAddr),sizeof serverAddr);if (iResult == SOCKET_ERROR) {cerr<<"綁定連接埠失敗"<<endl;}sockfd* sc = new sockfd;sc->fd = reciveSocket;sc->addr = reinterpret_cast<LPSOCKADDR>(&serverAddr);cout<<"------------------UDP伺服器端已啟動-----------------"<<endl;//開始接受訊息RecvMsg(sc);delete sc;closesocket(reciveSocket);WSACleanup();system("pause");return 0;}

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.