1. 程式人生 > >socket網路通訊詳細實現過程、解釋

socket網路通訊詳細實現過程、解釋

windows下實現socket通訊與Linux下有些不同,windows下需要初始化socket、確認WinSock DLL支援版本;以及標頭檔案等的使用。

下面是他們的區別之處:

標頭檔案 

windows使用的是winsock2.h標頭檔案
linux下netinet/in.h;unistd.h(close函式);sys/socket.h(常用函式:bind()、listen()等)

定義地址的不同(sockaddr_in的結構的區別)

windows下addr_var.sin_addr.S_un.S_addr
linux下addr_var.sin_addr.s_addr

初始化

windows下需要用WSAStartup啟動Ws2_32.lib,並且要用#pragma comment(lib,"Ws2_32")來告知編譯器連結該lib。

linux下不需要

關閉套接字的方式

windows下closesocket(sock)
linux下close(sock)

設定非阻塞方式

windows下ioctlsocket()函式
linux下fcntl(),在fcntl.h標頭檔案中

獲取錯誤碼 

windows下getlasterror()/WSAGetLastError()
linux下,未能成功執行的socket操作會返回-1;如果包含了errno.h,就會設定errno變數

讀取資料的函式

windows下為recv()函式
linux下為read()函式

服務端

#include <winsock2.h> 
#pragma comment(lib, "ws2_32") 
#define SPORT "8023"
#define SIP "127.0.0.1"
#define RECVLEN 1024 //接收的限制長度

#define MAXNUM 16 //最大連線個數

int myServer(){

	WORD wVersionRequested;
    WSADATA wsaData;

	int serverfd = -1 ,connfd = -1;
	int namelen = 0;
	int ret = 0;
	//客戶端請求的資料欄位
	char clientbuf[1024];
	int clientlen = 0;
	//我們需要完成的二進位制流的傳輸,需要使用unsigned char來實現,因為c裡沒有byte資料型別
	char * pbuf = NULL;//指向客戶端請求的資料的指標
	//伺服器返回的資料
	char serverbuf[1024];
	int serverlen = 0;
	//本地地址、埠賦值
	struct sockaddr_in sin;
	int optval = 1;



	/*
	Winsock2.h中
	#define AF_INET 0
	#define PF_INET AF_INET
	*/

	//PROTOCL FAMILY 協議族,使用AF_INET、PF_INET都可以
	sin.sin_family = PF_INET;

	//htons()函式將一個16位的無符號短整型資料由主機分列體式格式轉換成收集分列體式格式
	printf("埠:%s\n",SPORT);
	sin.sin_port = htons(atoi(SPORT));

	//inet_addr()函式將網路地址轉化為二進位制數字
	printf("地址:%s\n",SIP);
	sin.sin_addr.S_un.S_addr = inet_addr(SIP);
	
    namelen = sizeof sin;

	//WinSock初始化
    wVersionRequested=MAKEWORD(2, 2); //希望使用的WinSock DLL 的版本
    ret=WSAStartup(wVersionRequested, &wsaData);
    if(ret!=0)
    {
        printf("WSAStartup() failed!\n");
        return -1;
    }

	//伺服器端套接字
	//SOCK_STREAM是一個有序、可靠、面向連線的雙位元組流。它們是在AF_INET域中通過TCP/IP連線實現的。他們也是AF_UNIX域中常見的套接字型別。
	//IPPROTO_TCP 和 IPPROTO_IP代表兩種不同的協議,分別代表IP協議族裡面的TCP協議和IP協議
	serverfd = socket(sin.sin_family,SOCK_STREAM,IPPROTO_TCP);
	if(serverfd == INVALID_SOCKET){
		printf("建立server的socket套接字失敗serverfd:%d\n",serverfd);
		return -1;
	}

	//繫結本地地址、埠
	//sin:是一個指向包含有本機ip地址和埠號等資訊的sockaddr型別的指標
    //(const struct sockaddr*)&sin:由於結構體:sockaddr和sockaddr_in長度可以進行轉化,指向某個結構的指標可以指向另一個。
    ret = bind(serverfd,(const struct sockaddr*)&sin,sizeof(sin));
    if(ret == SOCKET_ERROR){
		printf("繫結本地地址、埠失敗\n");
		closesocket(serverfd); //關閉套接字
        WSACleanup();
		return -2;
	}

	//監聽客戶端的連線
	//不做特殊處理此時阻塞等待客戶端的請求連線
	ret = listen(serverfd,MAXNUM);
    if(ret == SOCKET_ERROR){
		printf("監聽客戶端的連線失敗\n");
		closesocket(serverfd); //關閉套接字
        WSACleanup();
		return -3;
	}
    //取消掉阻塞等待客戶端連線,我們可以這樣處理。
    //ioctlsocket(serverfd,FIONBIO,&optval);
    printf("正在等待客戶端的連線\n");
    
    //迴圈監聽客戶端的請求
	while(1){

		//分配新的connfd描述符和客戶端通訊
		connfd = accept(serverfd,(struct sockaddr*)&sin,&namelen);
		if(connfd == INVALID_SOCKET ){
			printf("和客戶端通訊失敗\n");
			closesocket(serverfd); //關閉套接字
			WSACleanup();
			return -4;
		}
	    
		//迴圈接收客戶端請求的資料
		memset(clientbuf,0x00,sizeof(clientbuf));
		clientlen = sizeof(clientbuf);
        pbuf = (char*)&clientbuf;

		while(clientlen > 0 ){
			//ret為客戶端請求資料的長度
			ret = recv(connfd,pbuf,RECVLEN,0);
			if(ret == SOCKET_ERROR){
				printf("讀取客戶端的請求資料失敗\n");
				closesocket(serverfd); //關閉套接字
				WSACleanup();
				return -1;
			}
			 //客戶端已經關閉連線{
			if (ret == 0){
				printf("客戶端已經沒有請求資料了\n");
				break;
			}
			clientlen -= ret;
			pbuf += ret;
			
		
		 }//while(clientlen > 0 ){迴圈接收客戶端請求的資料

	     printf("客戶端請求資訊:%s\n",clientbuf);
		 //服務端返回的內容
		 strcpy(serverbuf,"我已收到您的請求\n");
		 serverlen = sizeof(serverbuf);
		 ret = send(connfd,serverbuf,serverlen,0);
		if(ret == SOCKET_ERROR){
			printf("讀取客戶端的請求資料失敗\n");
			closesocket(serverfd); //關閉套接字
			WSACleanup();
			return -1;
		}
	}//while(1){迴圈監聽客戶端的請求
	//  closesocket(serverfd);
    //  WSACleanup();	
	return 0;

}

客戶端

#include "StdAfx.h"
#include <winsock2.h> 
#pragma comment(lib, "ws2_32") 
#define SPORT "8023"
#define SIP "127.0.0.1"

#define RECVLEN 1024 //接收的限制長度
int myClient(){
	WORD wVersionRequested;
    WSADATA wsaData;
	int clientfd = -1 ,acceptfd = -1;
	int namelen = 0;
	int ret = 0;
	//客戶端請求的資料欄位
	char clientbuf[1024];
	int clientlen = 0;
	//伺服器返回的資料
	char serverbuf[1024];
	int serverlen = 0;
	//本地地址、埠賦值
	struct sockaddr_in sin;
	int optval = 1;
	char * pbuf = NULL;//指向服務端端返回的資料的指標
	sin.sin_family=PF_INET;
	sin.sin_port = htons(atoi(SPORT));
	sin.sin_addr.S_un.S_addr= inet_addr(SIP);
    
    //WinSock初始化
	wVersionRequested = MAKEWORD(2, 2); //希望使用的WinSock DLL的版本
	ret = WSAStartup(wVersionRequested, &wsaData);
	if(ret!=0)
	{
		printf("WSAStartup() failed!\n");
		return -1;
	}
	//確認WinSock DLL支援版本2.2
	if(LOBYTE(wsaData.wVersion)!=2 || HIBYTE(wsaData.wVersion)!=2)
	{
		WSACleanup();
		printf("Invalid WinSock version!\n");
		return -1;
	}

    //建立客戶端套接字
	clientfd = socket(sin.sin_family,SOCK_STREAM,IPPROTO_TCP);
	if(clientfd == INVALID_SOCKET){
		printf("建立客戶端套接字失敗\n");
		return -1;
	}

    //向伺服器發起請求
	
	namelen = sizeof(sin);
	ret = connect(clientfd,(const struct sockaddr*)&sin,namelen);
    //此時會阻塞伺服器返回的應答,當有伺服器返回時才真正連線。
	if(ret== SOCKET_ERROR){
		closesocket(clientfd); //關閉套接字
        WSACleanup();
		printf("連線伺服器失敗\n");
	    return -2;
	}

	memset(clientbuf,0x00,sizeof(clientbuf));
	//客戶端輸入
	printf("請輸入您的請求:\n");
	scanf("%s",clientbuf);
    //strcpy(clientbuf,"客戶端請求。。");
    clientlen = sizeof(clientbuf);

	ret = send(clientfd,clientbuf,clientlen,0);
	if(ret == SOCKET_ERROR){
		closesocket(clientfd); //關閉套接字
        WSACleanup();
		printf("客戶端請求資料傳送失敗\n");
		return -3;
	}
    
	//迴圈接收服務端的請求資料
	memset(serverbuf,0x00,sizeof(serverbuf));
	serverlen = sizeof(serverbuf);
	pbuf = (char*)&serverbuf;

	while(serverlen > 0 ){
	//ret為客戶端請求資料的長度
	ret = recv(clientfd,pbuf,RECVLEN,0);
	if(ret == SOCKET_ERROR){
		printf("讀取伺服器的請求資料失敗\n");
		closesocket(clientfd); //關閉套接字
		WSACleanup();
		return -1;
	}
	 
	if (ret == 0){
		printf("服務端已經沒有返回資料了\n");
		break;
	}
	serverlen -= ret;
	pbuf += ret;


	}//while(clientlen > 0 ){迴圈接收客戶端請求的資料

	printf("服務端返回的資訊:%s\n",serverbuf);
    
	closesocket(clientfd); //關閉套接字
	return 0;
}

輸出結果