发表于|更新于
|阅读量:
基于Linux的套接字相关函数
在 linux 下,socket 也被认为是文件的一种。
文件的读写:
1 2 3 4 5
| #include <sys/types.h> #include <sys/stat.h> #include <fcnt1.h>
int open(const char* path, int flag);
|
1 2 3 4 5
| #include <unistd.h>
ssize_t write(int fd, const void* buf, size_t nbytes);
int close(int fd);
|
1 2
| #include <unistd.h> ssize_t read(int fd, void* buf, size_t nbytes);
|
open 函数的返回值即文件描述符,也即 close 函数的参数。
ps: size_t 是通过 typedef 声明的 unsigned int 类型;ssize_t 是通过 typedef 声明的 signed int 类型。
- 服务端套接字创建过程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| #include <sys/socket.h> #include <unistd.h>
int socket(int domaink, int type, int protocol);
int bind(int sockfd, struct sockaddr* myaddr, socklen_t addrlen);
int listen(int sockfd, int backlog);
int accept(int sockfd, struct sockaddr* addr, socklen_t* addrlen);
ssize_t write(int fd, const void* buf, size_t nbytes);
int close(int fd);
|
- 客户端套接字创建过程
1 2 3 4 5 6 7 8 9 10 11
| #include <sys/socket.h> #include <unistd.h>
int socket(int domaink, int type, int protocol);
int connect(int sockfd, struct sockaddr* serv_addr, socklen_t addrlen);
ssize_t read(int fd, void* buf, size_t nbytes);
int close(int fd);
|
基于Windows的套接字相关函数
1 2 3 4
| #include <WinSock2.h>
int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);
|
Windows 严格区分文件 I/O 函数和套接字 I/O 函数。
1 2 3 4 5
| #include <WinSock2.h>
int send(SOCKET s, const char* buf, int len, int flags);
int recv(SOCKET s, const char* buf, int len, int flags);
|
- 服务端套接字创建过程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| #include <WinSock2.h>
int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);
SOCKET socket(int af, int type, int protocol);
int bind(SOCKET s, const struct sockaddr* name, int namelen);
int listen(SCOKET s, int backlog);
SOCKET accept(SOCKET s, struct sockaddr* addr, int* addrlen);
int send(SOCKET s, const char* buf, int len, int flags);
int closesocket(SOCKET s);
int WSACleanup(void);
|
- 客户端套接字创建过程
1 2 3 4 5 6 7 8 9 10 11 12 13
| #include <WinSock2.h>
int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);
SOCKET socket(int af, int type, int protocol);
int connect(SOCKET s, const char* buf, int len, int flags);
int recv(SOCKET s, const char* buf, int len, int flags);
int closesocket(SOCKET s);
int WSACleanup(void);
|
套接字类型及协议
int socket(int domaink, int type, int protocol);
domain
1 2 3 4 5
| PF_INET IPv4 互联网协议族 PF_INET6 IPv6 互联网协议族 PF_LOCAL 本地通信的 UNIX 协议族 PF_PACKET 底层套接字的协议族 PF_IPX IPX Novell 协议族
|
type
面向连接的套接字 SOCK_STREAM,特点:可靠传输,有序传输,不存在数据边界
面向消息的套接字 SOCK_DGRAM,特点:快速船务,无序传输,有数据边界,限制传输数据大小
protocol
满足 PF_INET + SOCK_STREAM 的只有 IPPROTO_TCP
满足 PF_INET + SOCK_DGRAM 的只有 IPPROTO_UDP
地址族与数据序列
int bind(int sockfd, struct sockaddr* myaddr, socklen_t addrlen);
struct sockaddr_in
1 2 3 4 5 6 7 8 9 10 11 12
| struct sockaddr_in { sa_family_t sin_family; uint16_t sin_port; struct in_addr; char sin_zero[8]; }
struct in_addr { In_addr_t s_addr; }
|
sockaddr_in 成员分析
sin_family
sin_port
保存16位端口号,重点在于,它以网络字节序保存.
sin_addr
该成员保存32位IP地址信息,且也以网络字节序保存。
sin_zero
只为使 sockaddr_in 的大小与 sockaddr 结构体保持一致而插入的成员,必须填充为0。
使用举例:
1 2 3 4 5 6
| struct sockaddr_in serv_addr; serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); serv_addr.sin_port = htons(atoi(argv[1]));
int bind(serv_sock, (struct sockaddr*) serv_addr, sizeof(serv_addr));
|
网络字节序与地址变换
在通过网络传输数据时约定统一方式,这种约定称为网络字节序,统一为大端序。
字节序转换函数:
1 2 3 4
| unsigned short htons(unsigned short); unsigned short ntols(unsigned short); unsigned long htonl(unsigned long); unsigned long ntohl(unsigned long);
|
h
代表 host, n
代表 network, s
代表 short, l
代表 long
那么 htons 即“把short型数据从网络字节序转化为主机字节序”
网络地址的初始化与分配
将点分十进制格式的字符串转为32位整型数据的函数
1 2 3
| #include <arpa/inet.h>
in_addr_t inet_addr(const char* string);
|
与之类似的函数
1 2 3
| #include <arpa/inet.h>
int inet_aton(const char* string, struct in_addr* addr);
|
成功时返回1,失败返回0;string 含有需要转换IP地址信息的字符串地址,将保存转换结果的 in_addr 结构体变量的地址值。
请查看该函数的源码,了解一下是怎么转换的。
介绍一个相反的函数,将32位整型数据转化为点分十进制的IP字符串形式
1 2 3
| #include <arpa/inet.h>
char * inet_ntoa(struct in_addr adr);
|
失败时返回-1
网络地址初始化:
结合前面所述,套接字创建过程中常见的网络地址信息初始化方法如下:
1 2 3 4 5 6 7
| struct sockaddr_in addr; char* serv_ip = "211.217.168.13"; char* serv_port = "9190"; memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = inet_addr(serv_ip); addr.sin_port = htons(atoi(serv_port));
|
而采用 INADDR_ANY 的方式,则可自动获取运行服务器端的计算机 IP 地址。
1
| addr.sin_addr.s_addr = htonl(INADDR_ANY);
|
基于TCP的服务器端/客户端(1)
我们已经调用 bind 函数给套接字分配了地址和端口,接下来就要调用 listen 函数进入等待连接请求状态。
1 2 3
| #include <sys/socket.h>
int listen(int sock, int backlog);
|
sock 文件描述符
backlog 连接请求等待队列的长度
1 2 3
| #include <sys/socket.h>
int accept(int sock, struct sockaddr* addr, socklen_t* addrlen);
|
成功时返回创建的套接字文件描述符,失败时返回 -1。
该函数将自动创建一个新的套接字,并连接到发起请求的客户端。新的套接字用于数据 I/O。
而对于客户端来说,区别在于“请求连接”,可通过调用下面的函数发起连接请求。
1 2 3
| #include <sys/socket.h>
int connect(int sock, struct sockaddr* servaddr, socklen_t addrlen);
|
成功时返回 0,失败返回 -1
Hello world 服务器端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| int main(int argc, char* argv[]) { int serv_sock; int clnt_sock;
struct sockaddr_in serv_addr; struct sockaddr_in clnt_addr; socklen_t clnt_addr_size;
char message[] = "Hello World!";
if(argc != 2) { printf("Usage : %s <port>\n", argv[0]); exit(1); }
serv_sock = socket(PF_INET, SOCK_STREAM, 0); if(serv_sock == -1) error_handling("socket() error");
memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); serv_addr.sin_port = htons(atoi(argv[1]));
if(bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1) error_handling("bind() error");
if(listen(serv_sock, 5) == -1) error_handling("listen() error");
clnt_addr_size = sizeof(clnt_addr); clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size); if(clnt_sock == -1) error_handling("accept() error");
write(clnt_sock, message, sizeof(message)); close(clnt_sock); close(serv_sock); return 0; }
void error_handling(char* message) { fputs(message, stderr); fputc('\n', stderr); exit(1); }
|
Hello world 客户端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| int main(int argc, char* argv[]) { int sock;
struct sockaddr_in serv_addr; char message[30]; int str_len;
if(argc != 3) { printf("Usage : %s <IP> <port>\n", argv[0]); exit(1); }
serv_sock = socket(PF_INET, SOCK_STREAM, 0); if(serv_sock == -1) error_handling("socket() error");
memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = inet_addr(argv[1]); serv_addr.sin_port = htons(atoi(argv[2]));
if(connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1) error_handling("connect() error");
str_len = read(sock, message, sizeof(message)-1);
if(str_len == -1) error_handling("read() error");
printf("Message from server: %s \n", message); close(sock); return 0; }
void error_handling(char* message) { fputs(message, stderr); fputc('\n', stderr); exit(1); }
|
函数调用关系:
注意,客户端只能等到服务器端调用 listen 函数后才能调 connect 函数。同时要清楚,客户端调用 connect 函数前,服务器端可能率先调用 accept 函数。当然,此时服务器端在调用 accept 函数时进入阻塞状态,直到客户端调 connect 函数为止。