目錄
文章目錄
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
運行:
- 先啓動 TCP Server:
# ./tcp_server
- 查看監聽 Socket 是否綁定成功:
$ netstat -lpntu | grep 6666
tcp 0 0 0.0.0.0:6666 0.0.0.0:* LISTEN 28675/./tcp_server
- 啓動 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