目录
SOCKET编程
- 传统的进程间通信借助内核提供的IPC机制进行,但是只能限于本机通信,若要跨机通信,,就必须使用网络通信。( 本质上借助内核-内核提供了socket伪文件的机制实现通信----实际上是使用文件描述符),这就需要用到内核提供给用户的socket API函数库
- 既然提到socket伪文件,所以可以使用文件描述符相关的函数read write
- 使用socket会建立一个socket pair
- 如下图,一个文件描述符操作两个缓冲区, 这点跟管道是不同的, 管道是两个文件描述符操作一个内核缓冲区.
网络字节序
- 大端和小端的概念
- 大端: 低位地址存放高位数据, 高位地址存放低位数据
- 小端: 低位地址存放低位数据, 高位地址存放高位数据
- 大端和小端的使用使用场合:
- 大端和小端只是对数据类型长度是两个及以上的, 如int short, 对于单字节没限制, 在网络中经常需要考虑大端和小端的是IP和端口.
- 网络传输用的是大端法, 如果机器用的是小端法, 则需要进行大小端的转换
- 下面4个函数就是进行大小端转换的函数:
#include <arpa/inet.h> uint32_t htonl(uint32_t hostlong); uint16_t htons(uint16_t hostshort); uint32_t ntohl(uint32_t netlong); uint16_t ntohs(uint16_t netshort);
- 函数名的h表示主机host, n表示网络network, s表示short, l表示long
- 上述的几个函数, 如果本来不需要转换函数内部就不会做转换.
IP地址转换函数
- p->表示点分十进制的字符串形式
- to->到
- n->表示network网络
int inet_pton(int af, const char *src, void *dst);
- 函数说明: 将字符串形式的点分十进制IP转换为大端模式的网络IP(整形4字节数)
- 参数说明:
- af: AF_INET
- src: 字符串形式的点分十进制的IP地址
- dst: 存放转换后的变量的地址
- 例如: inet_pton(AF_INET, "127.0.0.1", &serv.sin_addr.s_addr);
- 手工也可以计算: 如192.168.232.145, 先将4个正数分别转换为16进制数,
- 192--->0xC0 168--->0xA8 232--->0xE8 145--->0x91
- 最后按照大端字节序存放: 0x91E8A8C0, 这个就是4字节的整形值.
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
- 函数说明: 网络IP转换为字符串形式的点分十进制的IP
- 参数说明:
- af: AF_INET
- src: 网络的整形的IP地址
- dst: 转换后的IP地址,一般为字符串数组
- size: dst的长度
- 返回值:
- 成功--返回指向dst的指针
- 失败--返回NULL, 并设置errno
- 例如: IP地址为010aa8c0, 转换为点分十进制的格式:
- 01---->1 0a---->10 a8---->168 c0---->192
- 由于从网络中的IP地址是高端模式, 所以转换为点分十进制后应该为: 192.168.10.1
socket编程用到的重要的结构体:struct sockaddr
- struct sockaddr结构说明:
struct sockaddr { sa_family_t sa_family; char sa_data[14]; }
- struct sockaddr_in结构:
struct sockaddr_in { sa_family_t sin_family; /* address family: AF_INET */ in_port_t sin_port; /* port in network byte order */ struct in_addr sin_addr; /* internet address */ }; /* Internet address. */ struct in_addr { uint32_t s_addr; /* address in network byte order */ }; //网络字节序IP--大端模式
- 通过man 7 ip可以查看相关说明
socket编程主要的API函数介绍
int socket(int domain, int type, int protocol);
- 函数描述: 创建socket
- 参数说明:
- domain: 协议版本
- AF_INET IPV4
- AF_INET6 IPV6
- AF_UNIX AF_LOCAL本地套接字使用
- type:协议类型
- SOCK_STREAM 流式, 默认使用的协议是TCP协议
- SOCK_DGRAM 报式, 默认使用的是UDP协议
- protocal:
- 一般填0, 表示使用对应类型的默认协议.
- 返回值:
- 成功: 返回一个大于0的文件描述符
- 失败: 返回-1, 并设置errno
- 当调用socket函数以后, 返回一个文件描述符, 内核会提供与该文件描述符相对应的读和写缓冲区, 同时还有两个队列, 分别是请求连接队列和已连接队列
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
- 函数描述: 将socket文件描述符和IP,PORT绑定
- 参数说明:
- socket: 调用socket函数返回的文件描述符
- addr: 本地服务器的IP地址和PORT,
struct sockaddr_in serv; serv.sin_family = AF_INET; serv.sin_port = htons(8888); //serv.sin_addr.s_addr = htonl(INADDR_ANY); //INADDR_ANY: 表示使用本机任意有效的可用IP inet_pton(AF_INET, "127.0.0.1", &serv.sin_addr.s_addr);
- addrlen: addr变量的占用的内存大小
- 返回值:
- 成功: 返回0
- 失败: 返回-1, 并设置errno
int listen(int sockfd, int backlog);
- 函数描述: 将套接字由主动态变为被动态
- 参数说明:
- sockfd: 调用socket函数返回的文件描述符
- backlog: 同时请求连接的最大个数(还未建立连接)
- 返回值:
- 成功: 返回0
- 失败: 返回-1, 并设置errno
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
- 函数说明:获得一个连接, 若当前没有连接则会阻塞等待.
- 函数参数:
- sockfd: 调用socket函数返回的文件描述符
- addr: 传出参数, 保存客户端的地址信息
- addrlen: 传入传出参数, addr变量所占内存空间大小
- 返回值:
- 成功: 返回一个新的文件描述符,用于和客户端通信
- 失败: 返回-1, 并设置errno值.
- accept函数是一个阻塞函数, 若没有新的连接请求, 则一直阻塞.
- 从已连接队列中获取一个新的连接, 并获得一个新的文件描述符, 该文件描述符用于和客户端通信. (内核会负责将请求队列中的连接拿到已连接队列中)
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
- 函数说明: 连接服务器
- 函数参数:
- sockfd: 调用socket函数返回的文件描述符
- addr: 服务端的地址信息
- addrlen: addr变量的内存大小
- 返回值:
- 成功: 返回0
- 失败: 返回-1, 并设置errno值
- 接下来就可以使用write和read函数进行读写操作了.
- 除了使用read/write函数以外, 还可以使用recv和send函数
- 读取数据和发送数据:
ssize_t read(int fd, void *buf, size_t count); ssize_t write(int fd, const void *buf, size_t count); 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);
- 对应recv和send这两个函数flags直接填0就可以了.
- 注意: 如果写缓冲区已满, write也会阻塞, read读操作的时候, 若读缓冲区没有数据会引起阻塞.
socket的API函数编写服务端和客户端程序的步骤图示
服务端开发流程
- 创建socket,返回一个文件描述符lfd---socket() --该文件描述符用于监听客户端连接
- 将lfd和IP PORT进行绑定----bind()
- 将lfd由主动变为被动监听----listen()
- 接受一个新的连接,得到一个文件描述符cfd----accept() ---该文件描述符是用于和客户端进行通信的
- while(1)
- {
- 接收数据---read或者recv
- 发送数据---write或者send
- }
- 关闭文件描述符----close(lfd) close(cfd);
客户端的开发流程
- 创建socket, 返回一个文件描述符cfd---socket() --该文件描述符是用于和服务端通信
- 连接服务端---connect()
- while(1)
- {
- //发送数据---write或者send
- //接收数据---read或者recv
- }
- close(cfd)
服务端程序示例
//服务端程序 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <unistd.h> #include <arpa/inet.h> #include <netinet/in.h> #include <ctype.h> int main() { //创建socket //int socket(int domain, int type, int protocol); int lfd = socket(AF_INET, SOCK_STREAM, 0); if(lfd<0) { perror("socket error"); return -1; } //int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); //绑定 struct sockaddr_in serv; bzero(&serv, sizeof(serv)); serv.sin_family = AF_INET; serv.sin_port = htons(8888); serv.sin_addr.s_addr = htonl(INADDR_ANY); //表示使用本地任意可用IP int ret = bind(lfd, (struct sockaddr *)&serv, sizeof(serv)); if(ret<0) { perror("bind error"); return -1; } //监听 //int listen(int sockfd, int backlog); listen(lfd, 128); //int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); struct sockaddr_in client; socklen_t len = sizeof(client); int cfd = accept(lfd, (struct sockaddr *)&client, &len); //len是一个输入输出参数 //const char *inet_ntop(int af, const void *src, char *dst, socklen_t size); //获取client端的IP和端口 char sIP[16]; memset(sIP, 0x00, sizeof(sIP)); printf("client-->IP:[%s],PORT:[%d]\n", inet_ntop(AF_INET, &client.sin_addr.s_addr, sIP, sizeof(sIP)), ntohs(client.sin_port)); printf("lfd==[%d], cfd==[%d]\n", lfd, cfd); int i = 0; int n = 0; char buf[1024]; while(1) { //读数据 memset(buf, 0x00, sizeof(buf)); n = read(cfd, buf, sizeof(buf)); if(n<=0) { printf("read error or client close, n==[%d]\n", n); break; } printf("n==[%d], buf==[%s]\n", n, buf); for(i=0; i<n; i++) { buf[i] = toupper(buf[i]); } //发送数据 write(cfd, buf, n); } //关闭监听文件描述符和通信文件描述符 close(lfd); close(cfd); return 0; }
- 测试(使用测试工具nc):
nc ip 端口号
客户端程序示例
//客户端代码 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <unistd.h> #include <arpa/inet.h> #include <netinet/in.h> int main() { //创建socket---用于和服务端进行通信 int cfd = socket(AF_INET, SOCK_STREAM, 0); if(cfd<0) { perror("socket error"); return -1; } //连接服务端 //int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); struct sockaddr_in serv; serv.sin_family = AF_INET; serv.sin_port = htons(8888); inet_pton(AF_INET, "127.0.0.1", &serv.sin_addr.s_addr); printf("[%x]\n", serv.sin_addr.s_addr); int ret = connect(cfd, (struct sockaddr *)&serv, sizeof(serv)); if(ret<0) { perror("connect error"); return -1; } int n = 0; char buf[256]; while(1) { //读标准输入数据 memset(buf, 0x00, sizeof(buf)); n = read(STDIN_FILENO, buf, sizeof(buf)); //发送数据 write(cfd, buf, n); //读服务端发来的数据 memset(buf, 0x00, sizeof(buf)); n = read(cfd, buf, sizeof(buf)); if(n<=0) { printf("read error or server closed, n==[%d]\n", n); break; } printf("n==[%d], buf==[%s]\n", n, buf); } //关闭套接字cfd close(cfd); return 0; }
- 测试(使用netstat):
- 【注】:参考黑马linux C++教程