1. 程式人生 > >Windows網路通訊(一):socket同步程式設計

Windows網路通訊(一):socket同步程式設計

網路通訊常用API

1. WSAStartup用於初始化WinSock環境

int WSAStartup(
  WORD      wVersionRequested,
  LPWSADATA lpWSAData
);

wVersionRequested:當前程序能夠使用Windows Socket的最高版本,目前指定2.2即可。

lpWSAData:指向一個WSAData結構體,接受Socket詳細資訊。

成功返回0

2. socket建立一個指定型別的SOCKET用於通訊

SOCKET WSAAPI socket(
  int af,
  int type,
  int protocol
);

af:address family,指定用於通訊的網路地址型別,可以取值AF_INET(IPv4),AF_INET6(IPv6),AF_BTH(藍芽)等。

type:指定傳輸型別,可以取值SOCK_STREAM(用於TCP),SOCK_DGRAM(用於UDP)等。

protocol:通訊協議,可以取值IPPROTO_TCP,IPPROTO_UDP等。

成功返回一個可以用於通訊的SOCKET,否則返回INVALID_SOCKET。

3. bind將socket和網路地址和埠繫結起來

int bind(
  SOCKET                s,
  const struct
sockaddr *name, int namelen );

s:一個未繫結的socket。

name:指向一個sockaddr物件,用於指定繫結的ip和埠資訊。

namelen:sockaddr的長度,為什麼這裡還需要指定長度呢,因為name是根據socket的型別來指定不同的結構體的,可能是sockaddr_in(IPv4)或者sockaddr_in6(IPv6)。

成功返回0

4. listen將SOCKET設為監聽狀態,可以被客戶端連線

int listen(
  SOCKET s,
  int    backlog
);

s:一個未被連線的socket

backlog:可以連線的客戶端的最大數目,如果指定為SOMAXCONN,則設定為最大的連線數量。

成功返回0

5. send通過指定socket傳送資料

int send(
  SOCKET s,
  const char   *buf,
  int    len,
  int    flags
);

s:一個已經連線的socket。

buf:待發送資料

len:待發送資料的長度

flags:傳送的一個標誌設定,一般設為0

成功返回已傳送的位元組數目。這個數目可能小於len的。失敗返回SOCKET_ERROR。

6. recv通過指定的socket接受資料

int recv(
  SOCKET s,
  char   *buf,
  int    len,
  int    flags
);

s:一個已經連線的socket

buf:接收資料的快取區

len:快取區長度

flags:接受資料的一個標誌,一般設為0。

成功返回已接受資料的長度,失敗返回SOCKET_ERROR,如果已經斷開連線,返回0

7. shutdown關閉一個SOCKET的send或者recv功能

int shutdown(
  SOCKET s,
  int    how
);

s:socket

how:指定該socket的某個功能不需要再使用,可以取值SD_RECEIVE(接收功能),SD_SEND(傳送功能),SD_BOTH(傳送和接收功能)。

成功返回0,失敗返回SOCKET_ERROR

8. connect連線到服務端,服務端開啟listen後,客戶端就可以使用connect進行連線

int connect(
  SOCKET                s,
  const struct sockaddr *name,
  int                   namelen
);

s:一個未連線的socket

name,namelen:和bind中name,namelen引數一樣

成功返回0,失敗返回SOCKET_ERROR

9. closesocket關閉一個已經存在的socket

int closesocket(
  SOCKET s
);

s:一個待關閉的socket。

成功返回0,失敗返回SOCKET_ERROR

10. accept接收一個來自客戶端的連線

SOCKET accept(
  SOCKET          s,
  struct sockaddr *addr,
  int             *addrlen
);

s:一個已經listen的socket

addr:用於儲存接收到的客戶端的sockaddr資訊

addrlen:連線的客戶端的sockaddr長度。

socket通訊示例

服務端和客戶端測試程式碼

// NetWork1.cpp : 定義控制檯應用程式的入口點。
//

#include "stdafx.h"
#include <stdio.h>
#include <tchar.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib, "ws2_32.lib")
#define DEFAULT_BUFLEN 512
#define DEFAULT_PORT 12345

//啟動客戶端
int startClient()
{
    SOCKET ConnectSocket = INVALID_SOCKET;
    struct sockaddr_in clientService; 
    char *sendbuf = "[Client]:客戶端測試文字";
    char recvbuf[DEFAULT_BUFLEN];
    int iResult;
    int recvbuflen = DEFAULT_BUFLEN;



    // 建立一個TCP套接字
    ConnectSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (ConnectSocket == INVALID_SOCKET) {
        printf("socket建立失敗: %ld\n", WSAGetLastError());
        WSACleanup();
        return 1;
    }

    // 指定連線埠和ip資訊
    clientService.sin_family = AF_INET;
    clientService.sin_addr.s_addr = inet_addr("127.0.0.1");
    clientService.sin_port = htons(DEFAULT_PORT);

    // 連線服務端
    iResult = connect(ConnectSocket, (SOCKADDR*)&clientService, sizeof(clientService));
    if (iResult == SOCKET_ERROR) {
        printf("連線失敗: %d\n", WSAGetLastError());
        closesocket(ConnectSocket);
        WSACleanup();
        return 1;
    }

    if (ConnectSocket == INVALID_SOCKET) {
        printf("無法連線到指定服務端!\n");
        WSACleanup();
        return 1;
    }

    // 傳送一段資料
    iResult = send(ConnectSocket, sendbuf, (int)strlen(sendbuf), 0);
    if (iResult == SOCKET_ERROR) {
        printf("傳送資料失敗: %d\n", WSAGetLastError());
        closesocket(ConnectSocket);
        WSACleanup();
        return 1;
    }

    printf("已傳送資料大小: %ld\n", iResult);

    // 關閉傳送功能,但是仍然可以接收
    iResult = shutdown(ConnectSocket, SD_SEND);
    if (iResult == SOCKET_ERROR) {
        printf("關閉傳送功能失敗: %d\n", WSAGetLastError());
        closesocket(ConnectSocket);
        WSACleanup();
        return 1;
    }

    // 接收資料
    do {

        iResult = recv(ConnectSocket, recvbuf, recvbuflen, 0);
        if (iResult > 0)
            printf("已接收: %d\n", iResult);
        else if (iResult == 0)
            printf("連線關閉\n");
        else
            printf("接收資料失敗: %d\n", WSAGetLastError());

    } while (iResult > 0);

    //關閉套接字
    closesocket(ConnectSocket);
    WSACleanup();
    return 0;
}

int startServer()
{
    int iResult;
    SOCKET ListenSocket = INVALID_SOCKET;
    SOCKET ClientSocket = INVALID_SOCKET;
    sockaddr_in service;
    int iSendResult;
    char recvbuf[DEFAULT_BUFLEN];
    int recvbuflen = DEFAULT_BUFLEN;
    char *sendbuf = "[Server]:服務端測試文字";

    // 建立TCP套接字
    ListenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (ListenSocket == INVALID_SOCKET) {
        printf("socket建立失敗: %ld\n", WSAGetLastError());
        WSACleanup();
        return 1;
    }
    // 設定繫結的ip和埠資訊
    service.sin_family = AF_INET;
    service.sin_addr.s_addr = inet_addr("127.0.0.1");
    service.sin_port = htons(DEFAULT_PORT);

    // 繫結套接字
    iResult = bind(ListenSocket, (SOCKADDR *)&service, sizeof(service));
    if (iResult == SOCKET_ERROR) {
        printf("套接字繫結失敗:%d\n", WSAGetLastError());
        closesocket(ListenSocket);
        WSACleanup();
        return 1;
    }

    iResult = listen(ListenSocket, SOMAXCONN);
    if (iResult == SOCKET_ERROR) {
        printf("套接字監聽失敗: %d\n", WSAGetLastError());
        closesocket(ListenSocket);
        WSACleanup();
        return 1;
    }

    // 接受來自客戶端的連線
    ClientSocket = accept(ListenSocket, NULL, NULL);
    if (ClientSocket == INVALID_SOCKET) {
        printf("accept failed with error: %d\n", WSAGetLastError());
        closesocket(ListenSocket);
        WSACleanup();
        return 1;
    }

    // 關閉服務端套接字,表示不需要再接收新的客戶端連線了,但是已經連線的套接字還是能通訊
    closesocket(ListenSocket);

    do {
        //接收來自客戶端的訊息
        iResult = recv(ClientSocket, recvbuf, recvbuflen, 0);
        if (iResult > 0) {
            printf("已接收資料大小: %d\n", iResult);

            // 傳送資料到客戶端,這裡就是將資料
            iSendResult = send(ClientSocket, sendbuf, (int)strlen(sendbuf), 0);
            if (iSendResult == SOCKET_ERROR) {
                printf("傳送失敗: %d\n", WSAGetLastError());
                closesocket(ClientSocket);
                WSACleanup();
                return 1;
            }
            printf("傳送位元組大小: %d\n", iSendResult);
        }
        else if (iResult == 0)
            printf("連線已關閉\n");
        else {
            printf("接收資料失敗: %d\n", WSAGetLastError());
            closesocket(ClientSocket);
            WSACleanup();
            return 1;
        }
    } while (iResult > 0);

    //關閉套接字以及清理套接字環境
    closesocket(ClientSocket);
    WSACleanup();

    return 0;
}
int main(int argc, char *argv[])
{
    int iResult;
    WSADATA wsaData;
    //初始化套接字環境
    iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (iResult != 0) {
        printf("初始化socket環境失敗: %d\n", iResult);
        return 1;
    }
    if (argc == 2 && strcmp(argv[1], "c") == 0)
    {
        //客戶端
        return startClient();
    }
    else if (argc == 2 && strcmp(argv[1], "s") == 0)
    {
        //服務端
        return startServer();
    }
    return 1;
}

執行結果

image

後記

以上只是一個簡單的socket通訊示例,所有api呼叫都是阻塞的,非阻塞呼叫將在下文寫出。

程式碼下載