Socket编程
传统的进程间通信借助内核提供的IPC机制进行,但只限于本机通信,若要跨机,必须使用网络通信(本质上也是借助内核提供的socket文件描述符)。
API函数
- 大小端转换(网络字节序与主机字节序转换)
#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表示网络net,s表示short,l表示long
- IP地址转换函数
//将点分十进制IP地址转换为大端网络字节序
int inet_pton(int af, const char* src, void* dst);
param:
af: AF_INET
src: 字符串形式的点分十进制IP地址
dst: 转换后的地址
//将大端网络字节序转换为点分十进制IP地址
const char* inet_ntop(int af, const void* src, char* dst, socklen_t size);
param:
af: AF_INET
src: 网络整形IP地址
dst: 转换后的字符串形式的点分十进制IP地址
size: dst长度
- 结构体sockaddr
man 7 ip
struct sockaddr{
sa_family_t sa_family;
char sa_data[14];
};
struct sockaddr_in{
sa_family_t sin_family;
in_port_t sin_port;
struct in_addr sin_addr;
}
struct in_addr{
uint32_t s_addr;
};
- socket
创建socket
int socket(int domain, int type, int protocol);
param:
domain: 协议版本
AF_INET, ipv4
AF_INET6, ipv6
type: 协议类型
SOCK_STREAM 流式,TCP
SOCK_DGRAM 报式,UDP
protocol: 0,表示使用对应类型的默认协议
当调用socket函数后,返回一个文件描述符,内核会提供与该文件描述符相对应的读和写缓冲区,同时还有两个队列,分别是请求连接队列和已连接队列
- bind
将socket文件描述符和IP、port绑定
int bind(int sockfd, const struct sockaddr* addr, socklen_t addrlen);
param:
sockfd: 套接字
addr: 本地IP地址和端口号
INADDR_ANY: 表示使用本机任意有效IP
addrlen: addr所占用的内存大小
- listen
将套接字由主动变为被动
int listen(int sockfd, int backlog);
param:
backlog: 同时请求连接的最大个数(还未建立连接)
- accept
获得一个连接,若当前没有连接则会阻塞
int accept(int sockfd, struct sockaddr* addr, socklen_t* addtlen);
param:
addr: 出参,保存客户端的地址信息
addrlen: 传入传出参数,addr变量占用的内存大小
return:
返回一个新的文件描述符,连接套接字,用于和客户端通信
accept函数是一个阻塞函数。
内核会负责将请求队列中的连接拿到已连接队列中,调用accept函数不是说新建一个连接,而是从已连接队列中取出一个可用连接。
- connect
连接服务器
int connect(int sockfd, const struct sockaddr* addr, socklen_t addrlen);
param:
addr: 服务端的地址信息
- 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);
注意:如果写缓冲区已满,write也会阻塞;若缓冲区没有数据,read也会阻塞
C/S模型(一对一)
一个客户端一个服务端的CS模型,多对一的模型(多进程、多线程、select、poll、epoll)见后续文章
客户端不需要绑定,绑定的话相当于固定了客户端的端口,不绑定表示内核随机分配一个未被占用的端口
查看监听状态和连接状态
netstat -anp
a: 显示所有
n: 以数字的方式显示
p: 显示进程信息
client代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<ctype.h>
#include<sys/types.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<netinet/in.h>
int main()
{
int cfd = socket(AF_INET, SOCK_STREAM, 0);
if(cfd < 0)
{
perror("socket error");
return -1;
}
struct sockaddr_in serv;
bzero(&serv, sizeof(serv));
serv.sin_family = AF_INET;
serv.sin_port = htons(8888);//要连接服务器的端口
inet_pton(AF_INET, "127.0.0.1", &serv.sin_addr.s_addr);//要连接的服务器IP地址
int ret = connect(cfd, (struct sockaddr*)&serv, sizeof(serv));
if(ret < 0)
{
perror("connect error");
return -1;
}
int n = 0;
char buf[1024];
while(1)
{
memset(buf, 0, sizeof(buf));
n = read(STDIN_FILENO, buf, sizeof(buf));
write(cfd, buf, n);
memset(buf, 0, sizeof(buf));
n = read(cfd, buf, sizeof(buf));
printf("n == [%d], buf == [%s]\n", n, buf);
if(n <= 0)
{
printf("read error or server close\n");
break;
}
}
close(cfd);
return 0;
}
server代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<ctype.h>
#include<sys/types.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<netinet/in.h>
int main()
{
int lfd = socket(AF_INET, SOCK_STREAM, 0);
if(lfd < 0)
{
perror("socket error");
return -1;
}
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);
int ret = bind(lfd, (struct sockaddr*)&serv, sizeof(serv));
if(ret < 0)
{
perror("bind error");
return -1;
}
listen(lfd, 128);
struct sockaddr_in client;
socklen_t len = sizeof(client);
int cfd = accept(lfd, (struct sockaddr*)&client, &len);
char ip[16];
memset(ip, 0, sizeof(ip));
printf("client: ip == [%s], port == [%d]\n", inet_ntop(AF_INET, &client.sin_addr.s_addr, ip, sizeof(ip)), ntohs(client.sin_port));
printf("lfd == [%d], cfd == [%d]\n", lfd, cfd);
int n = 0;
char buf[1024];
while(1)
{
memset(buf, 0, sizeof(buf));
n = read(cfd, buf, sizeof(buf));
if(n <= 0)
{
printf("read error or client close\n");
break;
}
printf("n == [%d], buf == [%s]\n", n, buf);
for(int i=0; i<n; i++)
{
buf[i] = toupper(buf[i]);
}
write(cfd, buf, n);
}
close(lfd);
close(cfd);
return 0;
}
【下一篇】:多进程版本