C++中的TCP通訊

這兩天簡單地看了下C++的socket通訊,提及socket通訊,就不得不說起TCP/IP 協議,這個協議大名鼎鼎,我想看過編程的至少據說過。在TCP/IP協議下,最多見的就是TCP和UDP,不過C++中的UDP我尚未看過,今天就簡單說說C++中的TCP通訊,大體分紅下面四部分:

  1. TCP簡介
  2. TCP通訊流程
  3. Windows下TCP通訊API的簡介
  4. TCP通訊的C++代碼

1,TCP簡介

TCP提供了一個徹底可靠的,面向鏈接的,全雙工的(包含兩個獨立且方向相反的鏈接)流傳輸服務,容許兩個應用程序創建一個鏈接,並在全雙工的方向上發送數據,而後終止鏈接。每個TCP鏈接均可靠的簡歷鏈接並完善的終止,在終止發生前,全部數據都會被可靠地傳送。

TCP通訊的客戶端和服務端每次通訊都會有3次握手的過程,這3次握手,確保數據可以準確地發送到對方。TCP通訊是分爲客戶端和服務端的。

2,TCP通訊流程

TCP通訊服務端和客戶端代碼是不一樣的。首先,服務端有一個ServerSocket,初始化之後(包括設置IP和端口,綁定監聽等過程),這些都設置好之後,就可使用accept()方法等待客戶端鏈接了,這個方法是阻塞的。一旦鏈接成功,就會返回一個新的Socket,使用這個Socket就能夠接收數據和發送數據了。客戶端自始始終都只有一個Socket,這個Socket初始化之後,使用connect()方法和服務器進行鏈接,鏈接成功後,這個Socket就能夠進行通訊了。

服務端

Created with Raphaël 2.1.0 調用WSAStartup函數初始化Winsock 調用socket函數建立一個服務端Socket 調用bind函數爲服務端socket指制定通訊對象 調用listen函數設置登臺鏈接狀態 調用accept函數接收客戶端的鏈接請求而且生成一個新的會話socket 調用send函數和recv函數進行會話 調用closesocket函數關閉socket 結束

客戶端

Created with Raphaël 2.1.0 調用WSAStartup函數初始化Winsock 調用socket函數建立一個Socket 調用connect函數與服務器創建鏈接 調用send函數和recv函數進行會話 調用closesocket函數關閉socket 結束

3,Windows下API簡介

在windows下進行TCP通訊,使用Ws2_32.dll動態連接庫。

1 . WSAStartup函數:該函數用於初始化Ws2_32.dll動態連接庫,在使用socket以前,必定要初始化該連接庫。
初始化:css

WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData)//第一個參數表示winsock的版本,本例使用的是winsock2.2版本。

2 . socket函數,建立一個socketios

//af:一個地址家族,一般爲AF_INET
//type:套接字類型,SOCK_STREAM表示建立面向流鏈接的套接字。爲SOCK_DGRAM,表示建立面向無鏈接的數據包套接字。爲SOCK_RAW,表示建立原始套接字
//protocol:套接字所用協議,不指定能夠設置爲0
//返回值就是一個socket
SOCKET socket(int af,int type,int protocol);

3 . bind函數:該函數用於將套接字綁定到指定的端口和地址。
第一個參數爲socket,第二個參數是一個結構指針,它包含了端口和IP地址信息,第三個參數表示緩衝區長度。須要說明的是,第二個參數在API中表示爲:const struct sockaddr FAR*,這個語法結構我還沒見過,網上說這是遠指針,win16時期的產物,算是長見識了。web

SOCKADDR_IN addrSrv;
 addrSrv.sin_family = AF_INET;
 addrSrv.sin_port = htons(8888); //1024以上的端口號
 addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);//IP地址
 bind(sockSrv, (LPSOCKADDR)&addrSrv, sizeof(SOCKADDR_IN));

4 . listen函數:將socket設置爲監聽模式,服務端的socket特有。必須將服務端的socket設置爲監聽模式才能和服務端簡歷鏈接。
裏面有兩個參數,第一個參數爲socket,第二個參數爲等待鏈接最大隊列的長度。編程

listen(sockSrv,10)

5 . accept函數:服務端socket接收客戶端的鏈接請求,鏈接成功,則返回一個socket,該socket能夠在服務端發送和接收數據。第一個參數爲socket,第二個參數爲包含客戶端端口IP信息的sockaddr_in結構指針,第三個參數爲接收參數addr的長度。windows

int len = sizeof(SOCKADDR);
accept(sockSrv, (SOCKADDR *) &addrClient, &len);

6 . closesocket函數:關閉socket,裏面的惟一的一個參數就是要關閉的socket。
7 . connect函數:客戶端socket發送鏈接請求的函數,第一個參數是客戶端的socket,第二個參數是一個結構體指針,裏面包括鏈接主機的地址和ip,第三個參數爲緩衝區的長度。api

connect(sockClient, (struct  sockaddr*)&addrSrv, sizeof(addrSrv));

8 . htons函數:將一個16位無符號短整型數據由主機排列方式轉化爲網絡排列方式,htonl函數的做用剛好相反。
9 . recv函數:接收數據,第一個參數爲socket,第二個參數爲接收數據緩衝區,第三個參數爲緩衝區的長度,第四個參數爲函數的調用方式。服務器

char buff[1024];
recv(sockClient, buff, sizeof(buff), 0);

10 . send函數:發送數據,裏面的參數基本和recv()同樣。網絡

代碼

服務端

#include <iostream>
#include <stdlib.h>
#include <stdio.h>
#include "winsock2.h"
 #include<cstdlib>
#pragma comment(lib,"ws2_32.lib")//引用庫文件
using namespace std;


char recvBuf[100];
SOCKET sockConn;
/** * 在一個新的線程裏面接收數據 */
DWORD WINAPI Fun(LPVOID lpParamter)
{
             while(true){
                memset(recvBuf, 0, sizeof(recvBuf));
                // //接收數據
                recv(sockConn, recvBuf, sizeof(recvBuf), 0);
                printf("%s\n", recvBuf);
            }
            closesocket(sockConn);
}

int main()
{
    WSADATA wsaData;
    int port = 8888;//端口號
    if(WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
    {
        printf("初始化失敗");
        return 0;
    }

    //建立用於監聽的套接字,即服務端的套接字
    SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, 0);

    SOCKADDR_IN addrSrv;
    addrSrv.sin_family = AF_INET;
    addrSrv.sin_port = htons(port); //1024以上的端口號
    /** * INADDR_ANY就是指定地址爲0.0.0.0的地址,這個地址事實上表示不肯定地址,或「全部地址」、「任意地址」。 通常來講,在各個系統中均定義成爲0值。 */
    addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);

    int retVal = bind(sockSrv, (LPSOCKADDR)&addrSrv, sizeof(SOCKADDR_IN));
    if(retVal == SOCKET_ERROR){
        printf("鏈接失敗:%d\n", WSAGetLastError());
        return 0;
    }

    if(listen(sockSrv,10) ==SOCKET_ERROR){
        printf("監聽失敗:%d", WSAGetLastError());
        return 0;
    }

    SOCKADDR_IN addrClient;
    int len = sizeof(SOCKADDR);

    while(1)
    {
        //等待客戶請求到來
        sockConn = accept(sockSrv, (SOCKADDR *) &addrClient, &len);
        if(sockConn == SOCKET_ERROR){
            printf("等待請求失敗:%d", WSAGetLastError());
            break;
        }

        printf("客戶端的IP是:[%s]\n", inet_ntoa(addrClient.sin_addr));

        //發送數據
        char sendbuf[] = "你好,我是服務端,我們一塊兒聊天吧";
        int iSend = send(sockConn, sendbuf, sizeof(sendbuf) , 0);
        if(iSend == SOCKET_ERROR){
            printf("發送失敗");
            break;
        }

        HANDLE hThread = CreateThread(NULL, 0, Fun, NULL, 0, NULL);
        CloseHandle(hThread);

    }

    closesocket(sockSrv);
    WSACleanup();
    system("pause");
    return 0;
}

客戶端:

#include <iostream>
#include <stdlib.h>
#include <stdio.h>
#include "winsock2.h"
#pragma comment(lib,"ws2_32.lib")//引用庫文件
using namespace std;

int main()
{
    //加載套接字
    WSADATA wsaData;
    char buff[1024];
    memset(buff, 0, sizeof(buff));

    if(WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
    {
        printf("初始化Winsock失敗");
        return 0 ;
    }

    SOCKADDR_IN addrSrv;
    addrSrv.sin_family = AF_INET;
    addrSrv.sin_port = htons(8888);//端口號
    addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//IP地址

    //建立套接字
    SOCKET sockClient = socket(AF_INET, SOCK_STREAM, 0);
    if(SOCKET_ERROR == sockClient){
        printf("Socket() error:%d", WSAGetLastError());
        return 0;
    }

    //向服務器發出鏈接請求
    if(connect(sockClient, (struct  sockaddr*)&addrSrv, sizeof(addrSrv)) == INVALID_SOCKET){
        printf("鏈接失敗:%d", WSAGetLastError());
        return 0;
    }else
    {
        //接收數據
        recv(sockClient, buff, sizeof(buff), 0);
        printf("%s\n", buff);
    }

    //發送數據
    char buffs[] = "下面我們開始聊天了";
    send(sockClient, buffs, sizeof(buffs), 0);
    //不斷輸入,而後發送
    while(true){
        cin>>buffs;
        send(sockClient, buffs, sizeof(buffs), 0);
    }

    //關閉套接字
    closesocket(sockClient);
    WSACleanup();//釋放初始化Ws2_32.dll所分配的資源。
    system("pause");//讓屏幕暫留
    return 0;
}

效果圖:
剛打開窗口eclipse

這裏寫圖片描述
有一個小問題就是使用cin輸入漢字的時候,偶爾會發生接收過程當中發生錯誤的現象。
我是在eclipse下寫得代碼,加載庫文件在eclipse下的配置請戳這裏:
加載庫文件在eclipse下的配置連接
關於打開窗口,只需打開項目目錄下debug文件下的對應的.exe文件便可打開窗口。使用eclipse下的控制檯連個程序並很差使。
最後,本篇文章的代碼參考了下面的博客:
博客地址
感謝分享!socket