C語言網絡編程——TCP


1.1 套接字

C語言網絡編程其實本質上也是多進程之間通過socket套接字進行通信,知識進程可能位於不同的服務器上,常用的TCP/IP協議有3種套接字類型,如下所示:

1.1.1 流套接字(SOCK_STREAM)

流套接字用於提供面向連接、可靠的數據傳輸服務,該服務保證數據能夠實現無差錯、無重復發送,並按照順序接受。流套接字之所以能偶實現可靠的數據服務,原因在於使用了TCP傳輸控制協議。

1.1.2 數據報套接字(SOCK_DGRAM)

數據包套接字提供了一種無連接的服務,該服務不能保證數據傳輸的可靠性,數據有可能在傳輸過程中丟失或者出現數據重復,且無法保證順序的接受數據。數據報套接字使用UDP進行數據傳輸。

1.1.3 原始套接字(SOCK_RAW)

原始套接字允許對較低層次的協議直接訪問,常用於檢驗新的協議實現,或者訪問現有服務中配置的新設備,因為器可以自如控制Window下的多種協議,能夠對網絡地城的傳輸機制進行控制,所以可以應用原始套接字來操縱網絡層和傳輸層應用。如:通過原始套接字接受發向本機的ICMP、IGMP,或者接受TCP/IP棧不能處理的IP包。

1.1.3 C語言套接字數據結構

套接字通常由三個參數構成:IP地址, 端口號、傳輸層協議。C語言進行套接字編程的時候,通常會使用sockaddr和sockaddr_in兩種數據類型,用於保存套接字信息。

struct sockaddr
{
	// 地址族,2字節
	unsigned short sa_family;
	// 存放地址和端口
	char sa_data[14];

}

struct sockaddr_in
{
	// 地址族
	short int sin_family;
	// 端口號
	unsigned short int sin_port;
	// 地址
	struct in_addr sin_addr;
	// 8字節數組,全為0,該字節數組的作用是為了讓兩種數據結構大小相同而保留的空字節
	unsigned char sin_zero[8];
}

對於sockaddr,大部分的情況下知識用於bind、connect、recvform、sendto等函數的參數,指明地址信息,在一般編程中,並不對此結構體直接操作,而是用sockaddr_in代替。
兩種數據結構中,地址族都占2個字節,常見的地址族AF_INET, AF_INET6, AF_LOCAL。這里要注意字節序的問題,建議使用以下函數來對端口和地址進行處理。

uint16_t htons(uint16_t bost16bit)
uint32_t htonl(uint32_t bost32bit)
uint16_t ntons(uint16_t net16bit)
uint32_t ntons(uint32_t net32bit)

1.2 基於TCP的網絡編程

客戶端和服務器的連接和三次握手發生在accept函數下,listen函數知識創建了socket的監聽模式。
在這里插入圖片描述
使用socket進行TCP通信時,經常使用的函數如下表所示。

函數 作用
socket 用於建立一個socket連接
bind 將socket與本機的一個端口綁定, 隨后可以在該端口監聽服務請求
connect 面向連接的客戶程序使用connect函數來配置socket,並於遠程服務器建立一個連接
listen 是socket處於被動監聽模式, 並為該socket建立一個輸入數據隊列,將到達服務器請求保存在此隊列中,直到程序處理他們
accept 讓服務器接收客戶端的連接請求
close 停止在該socket上的任何操作
send 數據發送函數
recv 數據接收函數

1.2.1 服務端實現

服務端程序流程如下:

  1. 使用socket()函數創建一個socket
  2. 使用bind()函數,綁定ip地址、端口等信息到socket上
  3. 使用listen()函數,設置允許的最大連接數
  4. 使用accept()函數,接收客戶端上來的連接
  5. 使用send()和recv()函數或read()和write()函數,收發數據
  6. 使用close()函數關閉連接
    實現代碼:
#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>

#define MAX_SIZE 512
#define PORT 3332

int main(void)
{
    int sockfd;
    int sock_fd;
    int recvnum;
    int addrlen = sizeof(struct sockaddr);
    struct sockaddr_in my_addr;
    struct sockaddr addr;
    char buf[MAX_SIZE];
	// 填充服務器端的數據,用於套接字綁定
    bzero(&my_addr, sizeof(struct sockaddr_in));
    my_addr.sin_family = AF_INET; // 設置為IPV4
    my_addr.sin_port = htons(PORT); // 將端口號主機序轉換為網絡序
    my_addr.sin_addr.s_addr = inet_addr("192.168.192.128"); // ip設置為192.168.192.128
	// 創建套接字
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        printf("create socket error!\n");
        exit(1);
    }
    // 綁定套接字
    if (bind(sockfd, (struct sockaddr *)&my_addr, addrlen) < 0)
    {
        printf("bind error!\n");
        exit(1);
    }
	// 監聽端口和ip,設置最大連接數為3
    if (listen(sockfd, 3) < 0)
    {
        printf("listen error!\n");
        exit(1);
    }
	// 建立服務器端和客戶端連接
    sock_fd = accept(sockfd, &addr, &addrlen);
    // 建立連接后,產生新的套接字
    if (sock_fd < 0)
    {
        printf("accept error!\n");
        exit(1);
    }
	// 接收數據
    if ((recvnum = recv(sock_fd, (void *)buf, MAX_SIZE, 0)) < 0)
    {
        printf("recv error!\n");
        exit(1);
    }
    buf[recvnum] = '\0';
    printf("recv from client: %s\n", buf);
    memset(buf, 0, MAX_SIZE);
    // 關閉連接
    close(sockfd);
    close(sock_fd);
    return 0;
}

1.2.2 客戶端實現

客戶端程序流程如下:

  1. 使用socket()函數,創建一個socket
  2. 設置要連接的服務端ip地址和端口等屬性
  3. 使用connect()函數,連接服務器端
  4. 使用send()和recv()函數或read()和write()函數,收發數據
  5. 使用close()函數關閉網絡連接
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>

#define MAX_SIZE 512
#define PORT 3332

int main()
{
    int sockfd;
    int addrlen = sizeof(struct sockaddr);
    char buf[MAX_SIZE];
    struct sockaddr_in serv_addr;
	// 填充服務器端數據
    bzero(&serv_addr, sizeof(struct sockaddr_in));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = ntohs(PORT);
    serv_addr.sin_addr.s_addr = inet_addr("192.168.192.128");
	// 創建套接字
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        printf("create socket error!\n");
        exit(1);
    }
	// 連接服務器端
    if (connect(sockfd, (struct sockaddr *)&serv_addr, addrlen) < 0)
    {
        printf("connect error!\n");
        exit(1);
    }
	// 發送數據到服務端
    memset(buf, 0, MAX_SIZE);
    printf("enter some text:");
    scanf("%s", buf);

    if (send(sockfd, (void *)buf, MAX_SIZE, 0) < 0)
    {
        printf("send error!\n");
        exit(1);
    }
	// 關閉連接
    close(sockfd);
    return 0;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM