網絡編程 — TCP/UDP Socket

目錄

Linux 下的 Socket API 接口

創建 Socket

獲得一個 Socket 文件描述符對象,文件描述符是 Linux 操作文件的句柄。在 Linux 中一切皆文件,Socket 文件描述符對象就是操作 Socket 的句柄。

int socket(int af, int type, int protocol);
  • af:AF(Address Family,地址族),IP 地址類型,常用的有 AF_INET 和 AF_INET6,其前綴也可以是 PF(Protocol Family),即PF_INET 和 PF_INET6。
  • type:數據傳輸方式,常用的有面向連接(SOCK_STREAM)方式(即 TCP) 和無連接(SOCK_DGRAM)的方式(即 UDP)。
  • protocol:傳輸協議,常用的有 IPPROTO_TCP 和 IPPTOTO_UDP,分別表示 TCP 傳輸協議和 UDP 傳輸協議。

創建 TCP 套接字:

int tcp_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

創建 UDP 套接字:

int udp_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);

綁定 Socket

將 Socket 與主機中的某個 IP:Port 綁定起來。

int bind(int sock, struct sockaddr *addr, socklen_t addrlen); 
  • sock:Socket 文件描述符。
  • addr:sockaddr 結構體變量的指針。
  • addrlen:addr 變量的大小,可由 sizeof() 計算得出。

將創建的套接字 ServerSock 與本地 IP、端口進行綁定:

int ServerSock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);

struct sockaddr_in ServerSockAddr;
memset(&ServerSockAddr, 0, sizeof(ServerSockAddr));

ServerSockAddr.sin_family = PF_INET;
ServerSockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
ServerSockAddr.sin_port = htons(1314); 

bind(ServerSock, (SOCKADDR*)&ServerSockAddr, sizeof(SOCKADDR));

其中 struct sockaddr_in 類型的結構體變量用於保存 IPv4 的 IP 信息。

struct in_addr {
	unsigned long a_addr;
}

struct sockaddr_in {
	unsigned short   	sin_family;     // 地址類型(2B)
	unsigned short int  sin_port;    	// 端口號(2B)
	struct in_addr      sin_addr;   	// IP 地址(4B)
	unsigned char       sin_zero[8]; 	// 填充空間(8B)
}

struct sockaddr {
      unsigned short  sa_family;	// 地址類型(2B)
      char            sa_data[14];	// 協議地址(14B)
 }

先初始化 sockaddr_in,再將它強制轉化成 sockaddr 來使用,例如 (SOCKADDR*)&ServerSockAddr,這裏涉及到了結構體之間的數據類型轉換。這兩個結構體,長度都爲 16 字節,sockaddr_in.sin_family 的數據存入 sockaddr.sa_family,剩下的 14 個字節存入 sockaddr.sa_data,這樣在各種操作中可以方便的處理端口號和 IP 地址。

若是 IPv6,則有對應的結構體:

struct sockaddr_in6 
{ 
    sa_family_t sin6_family;    // 地址類型,取值爲 AF_INET6
    in_port_t sin6_port;        // 16 位端口號
    uint32_t sin6_flowinfo;     // IPv6 流信息
    struct in6_addr sin6_addr;  // 具體的 IPv6 地址
    uint32_t sin6_scope_id;     // 接口範圍 ID
};

請求建立 Socket 連接

客戶端連接到服務端的某個 Socket,所以下述的 struct sockaddr *serv_addr 填充服務端的信息。

int connect(int sock, struct sockaddr *serv_addr, socklen_t addrlen);  

示例

int ClientSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
connect(ClientSock, (SOCKADDR*)&ServerSockAddr, sizeof(SOCKADDR));

監聽 Socket

服務端進程監聽 Socket 是否有新的,由客戶端發起的連接請求。

int listen(int sock, int backlog);
  • sock:需要進入監聽狀態的 Socket。
  • backlog:請求隊列的最大長度。

接受請求

服務端接受客戶端的連接請求,並返回一個客戶端的 Socket 文件描述符,該描述符作爲服務端操作對應該客戶端的連接的句柄。

int accept(int sock, struct sockaddr *addr, socklen_t *addrlen);
  • sock:服務器端套接字。
  • addr:sockaddr_in 結構體變量。
  • addrlen:addr 的長度,可由 sizeof() 求得。
  • 返回值:一個新的套接字,用於與客戶端進行通信。

示例

/* 監聽客戶端請求,accept() 返回一個新的套接字,發送和接收都是用這個套接字 */
int ClientSock = accept(ServerSock, (SOCKADDR*)&ClientAddr, &len);

關閉連接

服務端完成通信任務之後關閉與某個客戶端的連接,釋放資源。所以 int fd 傳入的是某個客戶端的 Socket 文件描述符。

int close(int fd);
  • fd:要關閉的文件描述符。

數據的發送和接收

  • read()/write()
  • recv()/send()
  • recvmsg()/sendmsg()
  • recvfrom()/sendto()
  • readv()/writev()

send 發送函數

ssize_t send(int sockfd, const void *buf, size_t len, int flags);
  • sockfd:要發送數據的套接字。
  • buf:保存發送數據的緩衝區地址。
  • len:要發送的數據的字節數。
  • flags:發送數據時的選項,常設爲 0。

recv 接收函數

ssize_t recv(int sockfd, void *buf, size_t len, int flags);
  • sockfd:要接收數據的套接字。
  • buf:保存接收數據的緩衝區地址。
  • len:要接收的數據的字節數。
  • flags:接收數據時的選項,常設爲 0。

sendto 發送函數

ssize_t sendto(int sock, void *buf, size_t nbytes, int flags, struct sockaddr *to, socklen_t addrlen);
  • sock:用於傳輸 UDP 數據的套接字;
  • buf:保存待傳輸數據的緩衝區地址;
  • nbytes:帶傳輸數據的長度(以字節計);
  • flags:可選項參數,若沒有可傳遞 0;
  • to:存有目標地址信息的 sockaddr 結構體變量的地址;
  • addrlen:傳遞給參數 to 的地址值結構體變量的長度。

recvfrom 接收函數

ssize_t recvfrom(int sock, void *buf, size_t nbytes, int flags, struct sockadr *from, socklen_t *addrlen);
  • sock:用於接收 UDP 數據的套接字;
  • buf:保存接收數據的緩衝區地址;
  • nbytes:可接收的最大字節數(不能超過 buf 緩衝區的大小);
  • flags:可選項參數,若沒有可傳遞 0;
  • from:存有發送端地址信息的 sockaddr 結構體變量的地址;
  • addrlen:保存參數 from 的結構體變量長度的變量地址值。

TCP Socket 示例

在這裏插入圖片描述

  • 服務端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <errno.h>

#define BUF_LEN 100    /* Size of buffer. */

/* Print exception information. */
#define ERR_MSG(errnum) do { \
    errnum = errno; \
    fprintf(stderr, "ERROR num: %d\n", errnum); \
    perror("PERROR message"); \
    fprintf(stderr, "STRERROR message: %s\n", strerror(errnum)); \
} while (0)

extern int errno;


int main(void)
{
    int server_fd = 0;
    int client_fd = 0;
    char buf[BUF_LEN] = {0};
    int addr_len = 0;
    int recv_len = 0;
    int optval = 1;

    struct sockaddr client_addr;
    memset(&client_addr, 0, sizeof(struct sockaddr));
    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(struct sockaddr));

    /* 創建 TCP 服務端 Socket 文件描述符。 */
    if (-1 == (server_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP))) {
        printf("socket ERROR.\n");
        ERR_MSG(errno);
        exit(1);
    }

    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);	// INADDR_ANY,即 0.0.0.0 表示監聽本機所有的 IP 地址,在生產環境中不建議使用。
    server_addr.sin_port = htons(6666);

    /* 設置地址和端口號可以重複使用,迴避了端口可能衝突的問題,在生產環境中不建議使用。*/
    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR,
                   &optval, sizeof(optval)) < 0) {

        printf("setsockopt ERROR.\n");
        ERR_MSG(errno);
        exit(1);
    }

    if (-1 == (bind(server_fd, (struct sockaddr*)&server_addr,
                    sizeof(struct sockaddr)))) {
        printf("bind ERROR.\n");
        ERR_MSG(errno);
        exit(1);
    }

    if (-1 == (listen(server_fd, 10))) {
        printf("listen ERROR.\n");
        ERR_MSG(errno);
        exit(1);
    }

    addr_len = sizeof(struct sockaddr);

    while (1) {
        /* 監聽某個客戶端的連接請求,返回客戶端 Socket 文件描述符,對該客戶端的發送和接收都使用這個套接字。 */
        if (-1 == (client_fd = (accept(server_fd,
                                       (struct sockaddr*)&client_addr,
                                       &addr_len)))) {
            printf("accept ERROR.\n");
            ERR_MSG(errno);
            exit(1);
        }

        if ((recv_len = recv(client_fd, buf, BUF_LEN, 0)) < 0) {
            printf("recv ERROR.\n");
            ERR_MSG(errno);
            exit(1);
        }

        printf("Client sent data %s\n", buf);

        send(client_fd, buf, recv_len, 0);

        /* 關閉套接字。 */
        close(client_fd);

        memset(buf, 0, BUF_LEN);
    }

    return 0;
}
  • 客戶端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <errno.h>


#define BUF_LEN 100    /* Size of buffer. */

/* Print exception information. */
#define ERR_MSG(errnum) do { \
    errnum = errno; \
    fprintf(stderr, "ERROR num: %d\n", errnum); \
    perror("PERROR message"); \
    fprintf(stderr, "STRERROR message: %s\n", strerror(errnum)); \
} while (0)

extern int errno;


int main(void)
{
    int client_fd;
    char buf[BUF_LEN] = {0};
    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(struct sockaddr));

    /* 連接到指定的服務端。 */
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    server_addr.sin_port = htons(6666);

    while (1) {
        if (-1 == (client_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP))) {
            printf("socket ERROR.\n");
            ERR_MSG(errno);
            exit(1);
        }

        /* 向指定的服務端發出連接請求。 */
        if (-1 == (connect(client_fd, (struct sockaddr*)&server_addr,
                           sizeof(struct sockaddr)))) {
            printf("connect ERROR.\n");
            ERR_MSG(errno);
            exit(1);
        }

        printf("Send to client >");
        gets(buf);
        send(client_fd, buf, BUF_LEN, 0);
        memset(buf, 0, BUF_LEN);

        recv(client_fd, buf, BUF_LEN, 0);
        printf("Receive from server: %s\n", buf);
        memset(buf, 0, BUF_LEN);
        close(client_fd);
    }

    return 0;
}

編譯:

gcc tcp_server.c -o tcp_server
gcc tcp_client.c -o tcp_client

運行:

  1. 先啓動 TCP Server:
# ./tcp_server
  1. 查看監聽 Socket 是否綁定成功:
$ netstat -lpntu | grep 6666
tcp        0      0 0.0.0.0:6666            0.0.0.0:*               LISTEN      28675/./tcp_server
  1. 啓動 TCP Client
# ./tcp_client

在這裏插入圖片描述

UDP 通信流程

·

  • 服務端
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define BUF_LEN  100

int main(void)
{
	int ServerFd;
	char Buf[BUF_LEN] = {0};
	struct sockaddr ClientAddr;
	struct sockaddr_in ServerSockAddr;
	int addr_size = 0; 
	int optval = 1; 
	
	/* 創建 UDP 服務端 Socket */
	if ( -1 == (ServerFd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)))
	{
		printf("socket error!\n");
		exit(1);
	}
	
	/* 設置服務端信息 */
    memset(&ServerSockAddr, 0, sizeof(ServerSockAddr)); 	// 給結構體ServerSockAddr清零
    ServerSockAddr.sin_family = AF_INET;  					// 使用IPv4地址
    ServerSockAddr.sin_addr.s_addr = htonl(INADDR_ANY); 	// 自動獲取IP地址
    ServerSockAddr.sin_port = htons(1314);  				// 端口
	
	// 設置地址和端口號可以重複使用  
    if (setsockopt(ServerFd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < 0)
	{
		printf("setsockopt error!\n");
		exit(1);
	}
	
	/* 綁定操作,綁定前加上上面的socket屬性可重複使用地址 */
    if (-1 == bind(ServerFd, (struct sockaddr*)&ServerSockAddr, sizeof(ServerSockAddr)))
	{
		printf("bind error!\n");
		exit(1);
	}
	
	addr_size = sizeof(ClientAddr);

	while (1)
	{
		/* 接受客戶端的返回數據 */
		int str_len = recvfrom(ServerFd, Buf, BUF_LEN, 0, &ClientAddr, &addr_size);
		
		printf("客戶端發送過來的數據爲:%s\n", Buf);
		
		/* 發送數據到客戶端 */
		sendto(ServerFd, Buf, str_len, 0, &ClientAddr, addr_size);
		
		/* 清空緩衝區 */
		memset(Buf, 0, BUF_LEN);  
	}
	
	close(ServerFd);

	return 0;
}
  • 客戶端
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define BUF_LEN  100

int main(void)
{
	int ClientFd;
	char Buf[BUF_LEN] = {0};
	struct sockaddr ServerAddr;
	int addr_size = 0;
	struct sockaddr_in  ServerSockAddr;
	
	/* 創建客戶端socket */
	if (-1 == (ClientFd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)))
	{
		printf("socket error!\n");
		exit(1);
	}
	
	/* 向服務器發起請求 */
    memset(&ServerSockAddr, 0, sizeof(ServerSockAddr));  
    ServerSockAddr.sin_family = PF_INET;
    ServerSockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    ServerSockAddr.sin_port = htons(1314);
	
	addr_size = sizeof(ServerAddr);
	
	while (1)
	{
		printf("請輸入一個字符串,發送給服務端:");
		gets(Buf);
		/* 發送數據到服務端 */
		sendto(ClientFd, Buf, strlen(Buf), 0, (struct sockaddr*)&ServerSockAddr, sizeof(ServerSockAddr));
		
		/* 接受服務端的返回數據 */
		recvfrom(ClientFd, Buf, BUF_LEN, 0, &ServerAddr, &addr_size);
		printf("服務端發送過來的數據爲:%s\n", Buf);
		
		memset(Buf, 0, BUF_LEN);   // 重置緩衝區
	}
	
	close(ClientFd);   // 關閉套接字
	
	return 0;
}

運行:

$ netstat -lpntu | grep 1314
udp        0      0 0.0.0.0:1314            0.0.0.0:*                           29729/./udp_server
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章