文章目錄
套接字
socket,套接字,它是計算機之間進行通信的一種約定或一種方式。通過 socket 這種約定,一臺計算機可以接收其他計算機的數據,也可以向其他計算機發送數據。
Linux下的套接字
Linux中創建的文件都有一個 int 類型的編號,稱爲文件描述符(File Descriptor)。使用文件時,只要知道文件描述符就可以。socket 也被認爲是文件的一種,和普通文件的操作沒有區別
Windows下的套接字
WinSock(Windows Socket)編程依賴於系統提供的動態鏈接庫(DLL),有兩個版本:
- 較早的DLL是 wsock32.dll,大小爲 28KB,對應的頭文件爲 winsock1.h;
- 最新的DLL是 ws2_32.dll,大小爲 69KB,對應的頭文件爲 winsock2.h。
加載ws2_32.dll
#pragma comment (lib, "ws2_32.lib")
WSAStartup()
使用DLL之前,還需要調用 WSAStartup() 函數進行初始化,以指明 WinSock 規範的版本
int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);
- wVersionRequested 爲 WinSock 規範的版本號,低字節爲主版本號,高字節爲副版本號(修正版本號)
- lpWSAData 爲指向 WSAData 結構體的指針。
eg:
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
建立Socket
Linux
int socket(int af, int type, int protocol);
- af ,地址族(Address Family),常用AF_INET(IPv4) 和 AF_INET6(IPv6)。
- type ,數據傳輸方式,常用的有 SOCK_STREAM(面向連接)和 SOCK_DGRAM(無連接)
- protocol 表示傳輸協議,常用的有 IPPROTO_TCP(TCP協議) 和 IPPTOTO_UDP(UDP協議)
1.TCP socket
int tcp_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
2.UDP socket
int udp_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
Windows
SOCKET sock = socket(AF_INET, SOCK_STREAM, 0); //創建TCP套接字
bind() 函數
將套接字與特定的IP地址和端口綁定起來
int bind(int sock, struct sockaddr *addr, socklen_t addrlen); //Linux
int bind(SOCKET sock, const struct sockaddr *addr, int addrlen); //Windows
示例
//創建套接字
int serv_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
//創建sockaddr_in結構體變量
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr)); //每個字節都用0填充
serv_addr.sin_family = AF_INET; //使用IPv4地址
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //具體的IP地址
serv_addr.sin_port = htons(1234); //端口
//將套接字和IP、端口綁定
bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
1、sockaddr_in 結構體
struct sockaddr_in{
sa_family_t sin_family; //地址族(Address Family),也就是地址類型
uint16_t sin_port; //16位的端口號
struct in_addr sin_addr; //32位IP地址
char sin_zero[8]; //不使用,一般用0填充
};
-
sin_family 和 socket() 的第一個參數的含義相同,取值也要保持一致。
-
sin_prot 爲端口號。需要用 htons() 函數轉換
-
sin_addr 是
struct in_addr
結構體類型的變量in_addr
結構體struct in_addr{ in_addr_t s_addr; //32位的IP地址 };
需要
inet_addr()
轉換serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
2、sockaddr
bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
bind()
第二個參數的類型爲 sockaddr
,而代碼中卻使用 sockaddr_in
,然後再強制轉換爲 sockaddr
,這是爲什麼呢?
sockaddr 結構體的定義如下:
struct sockaddr{
sa_family_t sin_family; //地址族(Address Family),也就是地址類型
char sa_data[14]; //IP地址和端口號
};
下圖是 sockaddr 與 sockaddr_in 的對比(括號中的數字表示所佔用的字節數):
sockaddr 和 sockaddr_in 的長度相同,都是16字節,只是將IP地址和端口號合併到一起,用一個成員 sa_data 表示。要想給 sa_data 賦值,必須同時指明IP地址和端口號,例如”127.0.0.1:80“,遺憾的是,沒有相關函數將這個字符串轉換成需要的形式,也就很難給 sockaddr 類型的變量賦值,所以使用 sockaddr_in 來代替。這兩個結構體的長度相同,強制轉換類型時不會丟失字節,也沒有多餘的字節。
connect() 函數
建立連接
與bind類似
int connect(int sock, struct sockaddr *serv_addr, socklen_t addrlen); //Linux
int connect(SOCKET sock, const struct sockaddr *serv_addr, int addrlen); //Windows
linsten() 函數
套接字進入被動監聽狀態
int listen(int sock, int backlog); //Linux
int listen(SOCKET sock, int backlog); //Windows
- sock 需要進入監聽狀態的套接字
- backlog 請求隊列的最大長度
accept() 函數
套接字處於監聽狀態時,接收客戶端請求,返回新的套接字
int accept(int sock, struct sockaddr *addr, socklen_t *addrlen); //Linux
SOCKET accept(SOCKET sock, struct sockaddr *addr, int *addrlen); //Windows
- sock 爲服務器端套接字
- addr 爲 sockaddr_in 結構體變量
- addrlen 爲參數 addr 的長度,可由 sizeof() 求得
Linux 接受和發送數據
write()
寫入數據
ssize_t write(int fd, const void *buf, size_t nbytes);
- fd 爲要寫入的文件的描述符
- buf 爲要寫入的數據的緩衝區地址
- nbytes 爲要寫入的數據的字節數。
read()
讀取數據
ssize_t read(int fd, void *buf, size_t nbytes);
- fd 爲要讀取的文件的描述符
- buf 爲要接收數據的緩衝區地址
- nbytes 爲要讀取的數據的字節數。
Windos 接受和發送數據
send()
發送數據
int send(SOCKET sock, const char *buf, int len, int flags);
- sock 爲要發送數據的套接字
- buf 爲要發送的數據的緩衝區地址
- len 爲要發送的數據的字節數
- flags 爲發送數據時的選項,一般設置爲 0 或 NULL
recv()
接受數據
int recv(SOCKET sock, char *buf, int len, int flags);
同上
示例
Windows
server
#include <stdio.h>
#include <winsock2.h>
#pragma comment (lib, "ws2_32.lib") //加載 ws2_32.dll
int main(){
//初始化 DLL
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
//創建套接字 PF_INET:IPv4
SOCKET servSock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
//綁定套接字
sockaddr_in sockAddr;
memset(&sockAddr, 0, sizeof(sockAddr)); //每個字節都用0填充
sockAddr.sin_family = PF_INET; //使用IPv4地址
sockAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); //具體的IP地址
sockAddr.sin_port = htons(1234); //端口
bind(servSock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR));
//進入監聽狀態
listen(servSock, 20);
//接收客戶端請求
SOCKADDR clntAddr;
int nSize = sizeof(SOCKADDR);
SOCKET clntSock = accept(servSock, (SOCKADDR*)&clntAddr, &nSize);
//向客戶端發送數據
char *str = "Hello World!";
send(clntSock, str, strlen(str)+sizeof(char), NULL);
//關閉套接字
closesocket(clntSock);
closesocket(servSock);
//終止 DLL 的使用
WSACleanup();
return 0;
}
client
#include <stdio.h>
#include <stdlib.h>
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")
//加載 ws2_32.dll
int main(){
//初始化DLL
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
//創建套接字
SOCKET sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
//向服務器發起請求
sockaddr_in sockAddr;
memset(&sockAddr, 0, sizeof(sockAddr)); //每個字節都用0填充
sockAddr.sin_family = PF_INET;
sockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
sockAddr.sin_port = htons(1234);
connect(sock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR));
//接收服務器傳回的數據
char szBuffer[MAXBYTE] = {0};
recv(sock, szBuffer, MAXBYTE, NULL);
//輸出接收到的數據
printf("Message form server: %s\n", szBuffer);
//關閉套接字
closesocket(sock);
//終止使用 DLL
WSACleanup();
system("pause");
return 0;
Linux
服務器端代碼 server.cpp
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
int main(){
//創建套接字
int serv_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
//將套接字和IP、端口綁定
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr)); //每個字節都用0填充
serv_addr.sin_family = AF_INET; //使用IPv4地址
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //具體的IP地址
serv_addr.sin_port = htons(1234); //端口
bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
//進入監聽狀態,等待用戶發起請求
listen(serv_sock, 20);
//接收客戶端請求
struct sockaddr_in clnt_addr;
socklen_t clnt_addr_size = sizeof(clnt_addr);
int clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);
//向客戶端發送數據
char str[] = "Hello World!";
write(clnt_sock, str, sizeof(str));
//關閉套接字
close(clnt_sock);
close(serv_sock);
return 0;
}
客戶端代碼 client.cpp:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
int main(){
//創建套接字
int sock = socket(AF_INET, SOCK_STREAM, 0);
//向服務器(特定的IP和端口)發起請求
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr)); //每個字節都用0填充
serv_addr.sin_family = AF_INET; //使用IPv4地址
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //具體的IP地址
serv_addr.sin_port = htons(1234); //端口
connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
//讀取服務器傳回的數據
char buffer[40];
read(sock, buffer, sizeof(buffer)-1);
printf("Message form server: %s\n", buffer);
//關閉套接字
close(sock);
return 0;
}