赞
踩
socket(套接字),用于网络中不同主机间进程的通信。
socket是一个伪文件,包含读缓冲区、写缓冲区。
socket必须成对出现。
socket可以建立主机进程间的通信,但需要协议(IPV4、IPV6等)、port端口、IP地址。
(1)创建流式socket套接字。
a)此socket套接字一直用于后续的监听连接。
b)socket函数。
(2)绑定本机IP地址和port。
b)bind函数。
(3)监听。
a)将socket套接字由主动变为被动。
b)创建未完成连接队列、已完成连接队列;未完成连接接经历3次握手才变成已完成连接。
c)listen函数。
(4)提取。
a)从已完成连接队列提取连接,创建一个新的已连接socket套接字用于和客户端通信。
b)accept函数。
(5)读写数据。
(6)关闭socket。
(1)创建流式socket套接字。
a)socket函数。
(2)连接服务器。
a)指定服务器的IP协议(IPV4或IPV6)、port、IP地址。
b)connect函数(该函数包含TCP的三次握手)。
(3)读写数据。
(4)关闭socket。
- #include<netinet/in.h>
-
- struct sockaddr_in {
- sa_family_t sin_family; /* 协议:AF_INET */
- in_port_t sin_port; /* 端口 */
- struct in_addr sin_addr; /* IP地址 */
- };
-
- /* Internet address. */
- struct in_addr {
- uint32_t s_addr; /* IP地址的网络字节序 */
- };
- #include<netinet/in6.h>
-
- 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 */
- };
为了接口通用,出现通用套接字结构体。
- #include<sys/socket.h>
-
- struct sockaddr {
- sa_family_t sa_family; /* AF_INET 或 AF_INET6 */
- char sa_data[14]; /* address data */
- };
- #include<sys/socket.h>
-
- int socket(int domain, int type, int protocol);
- /*
- 功能:
- 创建套接字
- 参数:
- domain:
- AF_INET
- AF_INET6
- 等等
- type:
- SOCK_STREAM:TCP流式套接字
- SOCK_DGRAM:UDP报式套接字
- SOCK_RAW:组包更多
- 等等
- protocol:0,自动填充
- 返回值:
- 成功:文件描述符
- 失败:-1
- */
- #include<sys/socket.h>
-
- int connect(int sockfd, const struct sockaddr* addr, socklen_t addrlen);
- /*
- 功能:
- 连接服务器
- 参数:
- sockfd:套接字文件描述符
- addr:IPV4套接字结构体地址 强转为通用套接字结构体
- 包含目的主机的IP和port
- addrlen:IPV4套接字结构体大小
- 返回值:
- 成功:0
- 失败:-1,并设置errno
- EACCES:权限不足或被防火墙拒绝
- EADDRINUSE:本地地址已被其他套接字使用
- ECONNREFUSED:远程主机拒绝连接
- ETIMEDOUT:连接超时
- */
- #include<sys/socket.h>
-
- int bind(int sockfd, const struct sockaddr* addr, socklen_t addrlen);
- /*
- 功能:
- 给套接字sockfd绑定固定的的IP地址和port
- 参数:
- sockfd:套接字文件描述符
- addr:IPV4套接字结构体地址
- addrlen:IPV4套接字结构体大小
- 返回值:
- 成功:0
- 失败:-1
- */
- #include<sys/socket.h>
-
- int listen(int sockfd, int backlog);
- /*
- 功能:
- 监听是否有客户端请求连接
- 参数:
- sockfd:套接字文件描述符
- backlog:已完成连接数量与未完成连接数量之和的最大值,一般写128
- 返回值:
- 成功:0
- 失败:-1
- */
- #include<sys/socket.h>
-
- int accept(int sockfd, struct sockaddr* addr, socklen_t* addrlen);
- /*
- 功能:
- 从已完成连接队列提取连接
- 参数:
- sockfd:套接字文件描述符
- addr:IPV4套接字结构体地址,以获取的客户端IP和port信息
- addrlen:存储IPV4套接字结构体大小的变量的地址。
- 返回值:
- 成功:新连接socket的文件描述符
- 失败:-1
- */
- #include<arpa/inet.h>
- #include<stdio.h>
- #include<sys/socket.h>
- #include<unistd.h>
-
- int main() {
-
- /* 1.创建socket */
- int sock_fd;
- sock_fd = socket(AF_INET, SOCK_STREAM, 0);
-
- /* 2.连接服务器 */
- // IPV4套接字结构体
- struct sockaddr_in addr;
- // IPV4
- addr.sin_family = AF_INET;
- // 服务器的port,转为网络字节序
- addr.sin_port = htons(8888);
- // 服务器IP地址,转为网络字节序存入addr.sin_addr.s_addr
- inet_pton(AF_INET, "192.168.0.11", &addr.sin_addr.s_addr);
- // 连接
- connect(sock_fd, (struct sockaddr*)&addr, sizeof(addr));
-
- /* 3.读写数据 */
- char buf[1024] = "";
- while (1) {
- int n = read(STDIN_FILENO, buf, sizeof(buf)); // 从终端读入buf
- write(sock_fd, buf, n);
- n = read(sock_fd, buf, sizeof(buf));
- write(STDOUT_FILENO, buf, n);
- }
-
- /* 4.关闭 */
- close(sock_fd);
-
- return 0;
- }
运行结果:
- #include<stdio.h>
- #include<arpa/inet.h>
- #include<sys/socket.h>
- #include<unistd.h>
- #include<stdlib.h>
- #include<string.h>
-
- int main(int argc, const char* argv[]) {
-
- /* 1.创建socket */
- int lfd = socket(AF_INET, SOCK_STREAM, 0);
-
- /* 2.绑定本机IP地址和port */
- struct sockaddr_in addr;
- addr.sin_family = AF_INET;
- addr.sin_port = htons(8000);
- // INADDR_ANY为0,表示绑定通配地址,即本地所有IP地址
- // addr.sin_addr.s_addr = INADDR_ANY;
- inet_pton(AF_INET, "192.168.124.128", &addr.sin_addr.s_addr);
- int ret = bind(lfd, (struct sockaddr*)&addr, sizeof(addr));
- if(ret < 0) {
- perror("bind");
- exit(0);
- }
-
- /* 3.监听 */
- listen(lfd, 128);
-
- /* 4.提取 */
- struct sockaddr_in cliaddr;
- socklen_t len = sizeof(cliaddr);
- int cfd = accept(lfd, (struct sockaddr*)&cliaddr, &len);
- char ip[16] = "";
- printf("新连接到来!IP:%s, port:%d\n",
- inet_ntop(AF_INET, &(cliaddr.sin_addr.s_addr), ip, 16),
- ntohs(cliaddr.sin_port));
-
- /* 5.读写 */
- char buf[1024] = "";
- while (1) {
- bzero(buf, sizeof(buf));
- int n = read(STDIN_FILENO, buf, sizeof(buf)); // 从终端读入buf
- write(cfd, buf, n);
- n = read(cfd, buf, sizeof(buf));
- printf("%s", buf);
- }
-
- /* 6.关闭 */
- close(lfd);
- close(cfd);
-
- return 0;
- }
运行结果:
注意:服务器进程被杀死,其占用的端口不会立即释放,再次连接会出现如下错误:
因为系统防止短时间内频繁开关相同的端口而将端口设置为TIME_WAIT状态(一般为2MSL,MSL一般为30s),因此TIME_WAIT大概持续60s;过了TIME_WAIT状态才可使用该端口。
若要立即使用该端口,可使用端口复用机制。
读写时,除了使用read、write函数,还可使用recv和send函数,用法类似于read和write,flags默认写0即可:
- #include<sys/types.h>
- #include<sys/socket.h>
-
- ssize_t recv(int sockfd, void* buf, size_t len, int flags);
-
- ssize_t send(int sockfd, const void* buf, size_t len, int flags);
将socket编程常用函数中的错误判断等封装为函数,使用更方便: mayueming1/socket-wrap-func: socket常用的包裹函数 (github.com)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。