一,socket簡介
1.什么是socket
2.網絡字節序
#include <stdio.h>int main()
{unsigned int num = 0x12345678;unsigned char *p = (unsigned char *)#if (*p == 0x78){printf("當前是小端機\n");}else if (*p == 0x12){printf("當前是大端機\n");}else{printf("無法判斷\n");}return 0;
}
二,基于TCP/IP協議的socket通信
socket抽象層與體系結構關系示意圖
1.?基于TCP/ip的相關通信api簡介

1)創建套接字
#include <sys/types.h> /* See NOTES */#include <sys/socket.h>//建??個新的socket(即為建??個通信端?)int socket(int domain, int type, int protocol);成功返回?負的套接字描述符,失敗返回 -1參數說明:domain:即協議域,?稱為協議族(family)Name Purpose Man pageAF_UNIX, AF_LOCAL Local communication unix(7)AF_INET IPv4 Internet protocols ip(7)AF_INET6 IPv6 Internet protocols ipv6(7)AF_IPX IPX - Novell protocolsAF_NETLINK Kernel user interface device netlink(7)AF_X25 ITU-T X.25 / ISO-8208 protocol x25(7)AF_AX25 Amateur radio AX.25 protocolAF_ATMPVC Access to raw ATM PVCsAF_APPLETALK AppleTalk ddp(7)AF_PACKET Low level packet interface packet(7)AF_ALG Interface to kernel crypto APItype:SOCK_STREAM TCPSOCK_DGRAM UDPSOCK_SEQPACKET 為最??度固定的數據報提供有序、可靠、基于雙向連接的數據
傳輸路徑SOCK_RAW 原始套接字SOCK_RDM 提供不保證排序的可靠數據報層。protocol:?于指定socket所使?的傳輸協議編號,通常默認設置為0即可0選擇type類型對應的默認協議;IPPROTO_TCP:TCP傳輸協議;IPPROTO_UDP:UDP傳輸協議;
2)綁定套接字和服務器地址
#include <sys/types.h> /* See NOTES */#include <sys/socket.h>//?來給參數sockfd的socket設置?個名稱,該名稱由addr參數指向的sockadr結構int bind(int sockfd, const struct sockaddr *addr, socklen_t addrle
n);返回說明:成功返回 0 失敗返回 -1?途:主要?與在TCP中的連接形參說明:sockfd 套接字?件描述符addr 服務器地址信息struct sockaddr {sa_family_t sa_family;char sa_data[14];}但在編程中?般使?下邊這種等價結構sockaddr,對于IPV4我們常?這個結構注意:使?該結構需要包含:#include <netinet/in.h>頭?件 ****struct sockaddr_in {sa_family_t sin_family; IPV4對應AF_INET//htons()u_int16_t sin_port; 端?號//sin_port存儲端?
號(使??絡字節順序)struct in_addr sin_addr; IP地址 //inet_addr()將
字符串形象ip轉?絡字節序};/* Internet address. */struct in_addr {u_int32_t s_addr; IP地址};addrlen addr的?度 sizeof(struct sockaddr)//如果使?IPV6地址,需要?這個結構來定義變量存放ipv6相關信息 struct sockaddr_in6 {sa_family_t sin6_family; /* AF_INET6 */in_port_t sin6_port; /* port number */uint32_t sin6_flowinfo; /* IPv6 flow information */struct in6_addr sin6_addr; /* IPv6 address */uint32_t sin6_scope_id; /* Scope ID (new in 2.4) */};struct in6_addr {unsigned char s6_addr[16]; /* IPv6 address */};
3)監聽模式
#include <sys/socket.h>//?于等待參數sockfd的scoket連線int listen(int sockfd, int backlog);返回值說明:成功返回0,失敗返回-1sockfd 套接字?件描述符backlog 監聽隊列?度(等待連接的客戶端的個數)缺省值20,最?值為128即為規定了內核應該為相應套接?排隊的最?連接個數
4)等待客戶端連接的到來
#include <sys/types.h> #include <sys/socket.h> //接收socket的連線int accept(int sockfd, struct sockaddr *addr, socklen_t *addrle
n); 返回值說明:成功返回連接的客戶端的套接字?件描述符 失敗返回 -1參數說明:sockfd 服務器套接字?件描述符addr 客戶端信息地址,做返回值?的,不獲取可以直接輸?NULLaddrlen addr的?度,注意是?個指針類型 ,傳?指定地址的?度,不指定
則NULL
5)客戶端建立socket連線
#include <sys/types.h> /* See NOTES */#include <sys/socket.h>/*?于將參數sockfd的socket連接到參數addr指定的?絡地址*/int connect(int sockfd, const struct sockaddr *addr, socklen_t addrl
en);addr:服務端的地址addrlen:為scokarr的結構?度返回值:返回0時,表明該連接已經成功建?,返回-1時,表明該連接發?了錯誤,即沒有成
功建?連接。注意:connect() 函數不阻塞,所以在使?之前要確保服務器已經進?等待連接狀態。
6)讀寫函數
#include<unistd.h>
//將數據寫?已打開的?件內,寫?count個字節到參數fd所指的?件內。
ssize_t write(int fd,const void*buf,size_t count);
//從已打開的?件中讀取數據
ssize_t read(int fd,void*buf,size_t count);
返回值:讀取到的實際數據數,如果返回0表示已經到達?件末尾或?可讀取的數據,當read()函數
返回值為0時,
表示對端已經關閉了 socket,這時候也要關閉這個socket,否則會導致socket泄露。
當read()或者write()函數返回值?于0時,表示實際從緩沖區讀取或者寫?的字節數?
當read()或者write()返回-1時,?般要判斷errno
?般是讀寫操作超時了,還未返回。這個超時是指socket的SO_RCVTIMEO與SO_SNDTIMEO兩個屬
性。
所以在使?阻塞socket時,不要將超時時間設置的過?。不然返回了-1,
你也不知道是socket連接是真的斷開了,還是正常的?絡抖動。?般情況下,阻塞的socket返回
了-1,
都需要關閉重新連接。
Close()和shutdown()——結束數據傳輸
當所有的數據操作結束以后,你可以調?close()函數來釋放該socket,從?
停?在該socket上的任何數據操作:close(sockfd);
2.并發服務器
示例程序:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <arpa/inet.h>
#include <netinet/in.h>#define PORT 8888
#define BUF_SIZE 1024// 線程函數:處理一個客戶端
void *handle_client(void *arg)
{int client_sock = *(int *)arg;free(arg);char buffer[BUF_SIZE];int n;while ((n = read(client_sock, buffer, BUF_SIZE - 1)) > 0){buffer[n] = '\0';printf("[客戶端消息] %s\n", buffer); // 在服務器端顯示客戶端消息}printf("客戶端斷開連接。\n");close(client_sock);return NULL;
}int main()
{int server_sock, *client_sock;struct sockaddr_in server_addr, client_addr;socklen_t client_len = sizeof(client_addr);// 創建socketserver_sock = socket(AF_INET, SOCK_STREAM, 0);if (server_sock < 0){perror("socket");exit(EXIT_FAILURE);}int opt = 1;setsockopt(server_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));server_addr.sin_family = AF_INET;server_addr.sin_addr.s_addr = INADDR_ANY;server_addr.sin_port = htons(PORT);if (bind(server_sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0){perror("bind");close(server_sock);exit(EXIT_FAILURE);}if (listen(server_sock, 5) < 0){perror("listen");close(server_sock);exit(EXIT_FAILURE);}printf("服務器已啟動,監聽端口 %d...\n", PORT);while (1){client_sock = malloc(sizeof(int));*client_sock = accept(server_sock, (struct sockaddr *)&client_addr, &client_len);if (*client_sock < 0){perror("accept");free(client_sock);continue;}printf("新客戶端連接:%s:%d\n",inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port));pthread_t tid;pthread_create(&tid, NULL, handle_client, client_sock);pthread_detach(tid);}close(server_sock);return 0;
}
client.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>#define PORT 8888
#define BUF_SIZE 1024int main()
{int sock;struct sockaddr_in server_addr;char buffer[BUF_SIZE];// 創建socketsock = socket(AF_INET, SOCK_STREAM, 0);if (sock < 0){perror("socket");exit(EXIT_FAILURE);}server_addr.sin_family = AF_INET;server_addr.sin_port = htons(PORT);server_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 服務器IPif (connect(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0){perror("connect");close(sock);exit(EXIT_FAILURE);}printf("已連接到服務器,現在可以輸入消息...\n");while (1){// 從標準輸入讀取消息printf("輸入: ");if (fgets(buffer, BUF_SIZE, stdin) == NULL)break;// 發送到服務器write(sock, buffer, strlen(buffer));}close(sock);return 0;
}
運行結果:
三,基于UDP/IP協議的socket通信
?基于UDP/IP通信的相關api簡介
1)發送UDP報格式數據
#include <sys/types.h>
#include <sys/socket.h>
//把UDP數據報發給指定地址
int sendto (int sockfd, const void *buf, int len, unsigned int flags, const struct sockaddr *to, int tolen);
參數說明:sockfd 套接字?件描述符buf 存放發送的數據len 期望發送的數據?度flags 0to struct sockaddr_in類型,指明UDP數據發往哪?報tolen: 對?地址?度,?般為:sizeof(struct sockaddr_in)。
2)接收UDP報格式數據
#include <sys/types.h>
#include <sys/socket.h>
//接收UDP的數據
int recvfrom(int sockfd, void *buf, int len, unsigned int flags, struct sockaddr *from, int *fromlen);
參數意義和sentdo類似,其中romlen傳遞是接收到地址的?度
例如 int p=sizeof(struct adrr_in),最后?個參數就傳為&p
3) 創建套接字 (Socket)
在進行任何網絡通信之前,應用程序必須先向操作系統請求一個網絡通信的“端點”,這個端點就是套接字。
#include <sys/types.h>
#include <sys/socket.h>// 創建一個套接字
int socket(int domain, int type, int protocol);```**參數說明:*** `domain`: 指定通信協議族。對于IPv4網絡通信,這個值通常是 `AF_INET`。
* `type`: 指定套接字的類型。* 對于UDP通信,這個值必須是 `SOCK_DGRAM` (Datagram),表示這是一個提供數據報服務的套接字,不建立連接,每個數據包都是獨立的。* (作為對比,TCP使用 `SOCK_STREAM`)
* `protocol`: 協議類型。通常設置為 `0`,讓系統根據 `domain` 和 `type` 自動選擇最合適的協議(對于 `AF_INET` 和 `SOCK_DGRAM`,系統會選擇IPPROTO_UDP)。**返回值:*** **成功**:返回一個新的文件描述符 (一個非負整數),代表創建的套接字。后續的所有操作都將通過這個文件描述符進行。
* **失敗**:返回 `-1`,并設置全局變量 `errno` 來表示錯誤原因。**示例:**
```c
int sockfd;
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {perror("socket creation failed");exit(EXIT_FAILURE);
}
4) 綁定地址和端口 (Bind)
對于需要接收數據的UDP程序(通常是服務器端),必須將其創建的套接字與一個具體的IP地址和端口號關聯起來。這樣,當網絡中有數據包發送到這個IP和端口時,操作系統才知道應該將數據交給哪個程序處理。
#include <sys/types.h>
#include <sys/socket.h>// 將套接字與一個地址綁定
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
參數說明:
sockfd:?socket()?函數返回的套接字文件描述符。
addr: 一個指向?struct sockaddr?結構體的指針,但實際使用中通常傳入一個?struct sockaddr_in?結構體(需要進行類型強制轉換)。該結構體包含了要綁定的IP地址和端口號。
addrlen:?addr?結構體的長度,通常為?sizeof(struct sockaddr_in)。
struct sockaddr_in?結構體詳解:
struct sockaddr_in {sa_family_t sin_family; // 地址族, 必須是 AF_INETin_port_t sin_port; // 端口號 (需要使用 htons() 轉換)struct in_addr sin_addr; // IPv4 地址
};struct in_addr {uint32_t s_addr; // 32位的IPv4地址 (需要使用 inet_addr() 或 INADDR_ANY)
};
注意:?端口號和IP地址必須從“主機字節序”轉換為“網絡字節序”。通常使用?htons()?(Host to Network Short) 來轉換端口,使用?inet_addr("127.0.0.1")?或?INADDR_ANY?(表示綁定到本機所有IP地址) 來設置地址。
返回值:
成功:返回?0。
失敗:返回?-1,并設置?errno。
示例:
struct sockaddr_in servaddr;
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = INADDR_ANY; // 監聽任意網卡的請求
servaddr.sin_port = htons(8888); // 綁定到8888端口if (bind(sockfd, (const struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) {perror("bind failed");exit(EXIT_FAILURE);
}
對于UDP客戶端,通常不需要顯式調用?bind()。因為在第一次調用?sendto()?時,操作系統會自動為其分配一個臨時的端口號。
5) 關閉套接字 (Close)
當通信結束時,應該釋放套接字占用的系統資源。這個操作就像關閉一個文件一樣。
#include <unistd.h>// 關閉一個文件描述符
int close(int fd);
參數說明:
fd: 需要關閉的文件描述符,這里就是?socket()?返回的套接字文件描述符。
返回值:
成功:返回?0。
失敗:返回?-1,并設置?errno。
示例:
printf("通信結束,關閉套接字。\n");
close(sockfd);
總結:一個典型UDP程序的API調用流程
服務器端:
socket(): 創建套接字。
bind(): 為套接字綁定一個固定的IP和端口,以便客戶端能找到它。
recvfrom(): 循環等待并接收來自客戶端的數據。
sendto(): (可選)向客戶端發送響應。
close(): 程序結束時關閉套接字。
客戶端:
socket(): 創建套接字。
sendto(): 向服務器的指定IP和端口發送數據。
recvfrom(): (可選)接收來自服務器的響應。
close(): 程序結束時關閉套接字。