「这是我参与2022首次更文挑战的第24天,活动详情查看:2022首次更文挑战」。
基础概念及函数原型
网络套接字 soket
一个文件描述符指向一个套接字,该套接字内部由内核借助两个缓冲区实现。
在网络通信中,套接字是成对出现的。
网络字节序:
小端法存储(数据存储方式):高位高地址,低位存低地址
int 0x12345678:
12
34
56
78
大端法存储:高位存低地址,低位存高地址
TCP/IP 协议规定:网络数据流英采用大端字节序,即:低地址高字节
htonl: 本地 转 网络(IP)
htons: 本地 转 网络(port)
ntohl: 网络 转 本地(IP)
ntohs: 网络 转 本地(port)
IP地址转换函数:
// 本地字节序转换为网络字节序
int inet_pton(int af, const char *src, void *dst);
参数:
1、af:AF_INET(TPv4) , AF_INET6(IPv6)
2、src: 传入参数,ip地址(点分十进制)
3、dst: 传出参数,转换后的网络字节序的IP地址
返回值:
成功:1
若 src 是无效的地址,返回 0
若 af 不包含有效的协议,则返回-1,并且设置 errno 为 EAFNOSUPPORT
// 网络字节序转换为本地字节序
const char *inet_ntop(int af, const void *src,char *dst, socklen_t size);
参数:
af: 同上
src: 传入参数,网络字节序的Ip地址
dst: 传出参数,转换后的本地字节序的ip地址
size: dst缓冲区大小
返回值:
成功: dst
失败: NULL
sockaddr 地址结构
/*
[int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);]
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 */
};
*/
struct sockaddr_in addr;
addr.sin_family = AF_INET/AF_INET6;
addr.sin_port = htons(9090);
addr.sin_addr.s_addr = htonl(INADDR_ANY); // 取出系统中有效的任意IP地址(二进制类型)
bind(fd,(struct sockaddr*)&addr,size);
socket函数
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
参数:
1、domin: AF_INET, AF_INET6, AF_UNIX
2、type: SOCK_STREAM , SOCK_DGRAM
3、protocal: 0(一般默认传0)
返回值:
成功:返回新套接字所对应的文件描述符
失败:-1,设置errno
bind函数
#include <sys/socket.h>
给socket 绑定一个地址结构(IP+端口号)
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
参数:
1、sockfd: 文件描述符
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(8888);
addr.sin_addr.s_arrd = htonl(INADDR_ANY);
2、addr: (struct sockaddr *)&addr
3、addrlen: sizeof(addr) 地址结构的大小
返回值:
成功:0
失败:-1,设置errno
listern 函数
设置同时与服务器建立链接的上限数,(同时进行3次握手的客户端数量)
int listen(int sockfd, int backlog);
参数:
1、sockfd: socket()函数的返回值
2、backlog: 上限数值,最大为128
返回值:
成功:0
失败:-1,设置errno
accept 函数
阻塞等待客户端建立连接,成功的话,返回一个与客户端成功连接的socket描述符
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数:
1、sockfd: socket()函数的返回值
2、addr: 传出参数,成功与服务器建立连接的那个客户端的地址结构(IP+端口号)
socket_t client_addr_len = sizeof(addr);
3、addrlen: 传入传出(client_addr_len),入:addr的大小,出:客户端addr的实际大小。
返回值:
成功:能与服务器进行数据通信的 socket 文件描述符
失败:-1,设置errno
connect函数
使用现有的 socket 与服务器进行连接
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen)
参数:
1、sockfd: socket函数返回值
2、addr: 传入参数,服务器的地址结构
3、addrlen: 服务器地址结构的大小
返回值:
成功: 0
失败:-1,设置errno
如果不使用 bind 绑定客户端地址结构,系统会采用 “隐式绑定”
TCP流程分析
server端:
1、socket():创建socket
2、bind(): 绑定服务器的地址结构
3、listen(): 设置监听上限
4、accept(): 阻塞监听客户端连接
5、read(): 读从accept返回的socket中的数据
6、小写-》大写 toupper()
7、write()
8、close()
client端:
1、socket(): 创建socket
2、connect(): 与服务器建立连接
3、write(): 写数据到socket
4、read(): 读转换后的数据
5、close()
测试案例
实现字符串小写转大写(客户端出入小写,服务端返回大写)
// server 端 : server.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <ctype.h>
#define PORT 9527
// 检查错误
void sys_error(int ret, const char *str)
{
if (ret == -1)
{
perror(str);
exit(1);
}
}
int main()
{
// server fd , connection fd
int lfd = 0, cfd = 0;
int ret, i; // 检测返回值
int clit_addr_len; // 客户端地址长度
char buf[BUFSIZ];
struct sockaddr_in serv_addr, clit_addr;
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT);
serv_addr.sin_addr.s_addr = ntohl(INADDR_ANY);
lfd = socket(AF_INET, SOCK_STREAM, 0);
sys_error(lfd, "socket error");
// 绑定
ret = bind(lfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
sys_error(ret, "bind error");
// 监听
ret = listen(lfd, 128);
sys_error(ret, "listen error");
// 客户端地址长度即时传入值又是传出值,这里先赋予初值
clit_addr_len = sizeof(clit_addr);
// 与客户端建立连接,返回客户端的 socket 描述符
cfd = accept(lfd, (struct sockaddr *)&clit_addr, &clit_addr_len);
sys_error(cfd, "accept error");
while (1)
{
ret = read(cfd, buf, sizeof(buf));
// 写出到标准输出
write(STDOUT_FILENO, buf, ret);
for (i = 0; i < ret; i++)
{
buf[i] = toupper(buf[i]);
}
write(cfd, buf, ret);
}
close(lfd);
close(cfd);
return 0;
}
运行结果
服务端:./server
客户端:借助 系统工具 nc(net connection) 来进行测试