学习视频链接
黑马程序员-Linux网络编程_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1iJ411S7UA?p=17
目录
一、网络套接字(英文socket)
1.1 原理图
1.2 简介
二、网络字节序
2.1 大小端法
2.2 函数介绍
三、IP地址转换函数
3.1 inet_pton
3.2 inet_ntop
四、sockaddr 数据结构
4.1 简介
4.2 新建一个sockaddr
五、socket流程和相关函数
5.1 流程图
5.2 socket函数
5.3 bind函数
5.4 listen函数
5.5 accept函数
5.6 connect函数
六、server的实现
6.1 建立C语言项目
6.2 代码
6.3 测试运行
七、client 客户端的实现
7.1 代码
7.2 测试
八、错误处理函数封装
8.1 思路
8.2 测试代码(代码优点问题,后面再修改)
虚线方框表示的是一个套接字
类似于管道文件。一个文件描述符指向一个套接字(套接字内部由内核提供两个缓冲区实现)
在通信过程中,套接字一定是成对出现的(Linux主机上有,客户端主机上也有)
小端法:(pc本地存储) 高位存高地址。地位存低地址
大端法:(网络存储) 高位存低地址。地位存高地址
计算机采用小端法
网络采用大端法
因为小端法在计算机网络传播中要一直被路由解码,很麻烦,所以计算机上使用小端法存储的数据要转换成大端法的数据,再发送到网络上
小端字节序不太清楚是什么,可以看我在知乎上面记的笔记
《x86汇编语言》第2章 让计算机执行处理器指令 - 知乎 (zhihu.com)https://zhuanlan.zhihu.com/p/521534699
1、函数
为使网络程序具有可移植性,使同样的 C 代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和本地字节序的转换
#include
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
htonl:本地字节序 ——> 网络字节序 (32 位,主要转换的是 IP)
htons:本地字节序 ——> 网络字节序 (16 位,主要转换的是 port)
ntohl:网络字节序 ——> 本地字节序 (IP)
ntohs:网络字节序 ——> 本地字节序 (port)
2、对上面函数进行封装
192.168.1.11 本质是一个 string,通过 atoi 把这 string 转换成 int 整数,再调用 htonl 转换成网络字节序的整个流程可以用下面的函数代替
#include
int inet_pton(int af, const char *src, void *dst);
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
该函数支持 IPv4 和 IPv6
1、作用
本地字节序 (string IP) ——> 网路字节序(二进制)
2、int inet_pton(int af, const char *src, void *dst);
(1) 参数
af:AF_INET、AF_INET6
src:传入参数,IP 地址(点分十进制:192.168.10.100)
dst:传出参数,转换后的网络字节序的 IP 地址
(2) 返回值:
成功:1
异常:0,说明 src 指向的不是一个有效的 ip 地址
失败:-1
1、作用
网络字节序(二进制)——> 本地字节序 (string IP)
2、const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
(1) 传入参数
af:AF_INET、AF_INET6
src:传入参数,网络字节序 IP 地址
dst:传出参数,本地字节序 (string IP)
size:传出参数,dst 的大小
(2) 返回值
成功:dst
失败:NULL
strcut sockaddr 很多网络编程函数诞生早于 IPv4 协议,那时候都使用的是 sockaddr 结构体,为了向前兼容,现在 sockaddr 退化成了 (void*) 的作用,传递一个地址给函数,至于这个函数是sockaddr_in 还是 sockaddr_in6,由地址族确定,然后函数内部再强制类型转化为所需的地址类型
通过 man 7 ip 查看
struct sockaddr_in addr;
addr.sin_family = AF_INET/AF _INET6; // 选择 ip 类型 ipv4、ipv6、man 7 ip 查看开发者手册
addr.sin_port = htons(9527); // 选择端口号
// 写法1:选择 ip 地址
int dst;
inet_pton(AF_INET, "192.157.22.45", (void*)&dst);
addr.sin_addr.s_addr = dst;
// 写法2:选择 ip 地址
addr.sin_addr.s_addr = htonl(INADDR_ ANY); // 取出系统中有效的任意IP地址,二进制类型(可以转换成字节序)
bind(fd, (struct sockaddr *)&addr, size);
server:
1、socket() | 创建socket |
2、bind() | 绑定服务器地址结构 |
3、listen() | 监听上限 |
4、accept() | 阻塞监听客户端连接 |
5、read() | 读socket获取客户端数据 |
6、小写—>大写 | toupper() |
7、write(fd) | |
8、close() |
client:
1、socket() | 创建socket |
2、connect() | 与服务器建立连接 |
3、write() | 写数据到socket |
4、read() | 读转换后的数据 |
5、显示读取结果 | |
6、close() |
1、作用
创建一个套接字
2、#include
int socket(int domain, int type, int protocol);
(1) 参数
domain:AF_INET、AF_INET6、AF_UNIX(ip地址协议:ipv4、ipv6、本地套接字)
type:SOCK_STREAM、SOCK_DGRAM(数据传输协议:流式协议、报式协议)
protocol:0(选择流式协议或报式协议的代表协议,流式协议的代表协议为TCP,报式协议的代表协议为UDP)
SOCK_STREAM与SOCK_DGRAM套接字 - 简书 (jianshu.com)https://www.jianshu.com/p/046f37121259
(2) 返回值
成功:新套接字所对应文件描述符
失败:-1 errno
1、作用
给 socket 绑定一个地址结构 (IP +port)
2、#include
#include
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
(1) 参数
sockfd:socket 函数返回值
addr.sin_family = AF_INET;
addr.sin_port = htons(8888);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr:传入参数(struct sockaddr *)&addr
addrlen:sizeof(addr) 地址结构的大小
(2) 返回值
成功:0
失败:-1 errno
1、作用
设置同时与服务器建立连接的上限数 (同时进行 3 次握手的客户端数量)
2、int listen(int sockfd, int backlog);
(1) 参数
sockfd:socket 函数返回值
backlog:上限数值。最大值 128
(2) 返回值:
成功:0
失败:-1 errno
1、作用
阻塞等待客户端建立连接,成功的话,返回一个与客户端成功连接的 socket 文件描述符
2、int accept(int sockfd, struct sockaddr *addr,socklen_t *addrlen);
(1) 参数
sockfd:socket 函数返回值
addr:传出参数。成功与服务器建立连接的那个客户端的地址结构 (IP + port)
socklen_t clit_addr_len = sizeof(addr);
addrlen:传入传出。&clit_addr_len 入:addr 的大小。 出:客户端 addr 实际大小
(2) 返回值
成功:能与服务器进行数据通信的 socket 对应的文件描述
失败:-1 errno
1、作用
使用现有的 socket 与服务器建立连接
2、int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
(1) 参数
sockfd:socket 函数返回值
addr:传入参数。服务器的地址结构
(2) 返回值:
成功:0
失败:-1 errno
如果不适用 bind 绑定客户端地址结构,采用 "隐式绑定"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define SERV_PORT 9527
void sys_err(const char *str)
{
perror(str);
exit(1);
}
int main(void)
{
int lfd = 0, cfd = 0;
int ret, i;
char buf[BUFSIZ], client_IP[1024]; // BUFSIZ 系统自带的宏,大小 4096
struct sockaddr_in serv_addr, clit_addr;
socklen_t clit_addr_len;
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(SERV_PORT);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
// AF_INET: ipv4
// SOCK_STREAM: 提供面向连接的稳定数据传输,即TCP协议
lfd = socket(AF_INET, SOCK_STREAM, 0);
if (lfd == -1) {
sys_err("socket error");
}
// 给socket绑定地址
bind(lfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
// 最大连接数
listen(lfd, 128);
clit_addr_len = sizeof(clit_addr);
cfd = accept(lfd, (struct sockaddr *)&clit_addr, &clit_addr_len);
if (cfd == -1) {
sys_err("accept error");
}
printf("client ip:%s port:%d\n",
inet_ntop(AF_INET, &clit_addr.sin_addr.s_addr, client_IP, sizeof(client_IP)),
ntohs(clit_addr.sin_port));
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;
}
#include
#include
#include
#include
#include
#include
#include
#include
#define SERV_PORT 9527
void sys_err(const char *str)
{
perror(str);
exit(1);
}
int main(void)
{
int cfd;
int conter = 10;
char buf[BUFSIZ];
struct sockaddr_in serv_addr; // 服务器地址结构
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(SERV_PORT);
inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr.s_addr);
cfd = socket(AF_INET, SOCK_STREAM, 0);
if (cfd == -1) {
sys_err("socket error");
}
int ret = connect(cfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
if (ret != 0) {
sys_err("connect error");
}
while (--conter) {
write(cfd, "hello\n", 6);
ret = read(cfd, buf, sizeof(buf));
write(STDOUT_FILENO, buf, ret);
sleep(1);
}
close(cfd);
return 0;
}
写一个函数来代替原来的函数,这个函数拥有原来的函数的功能,并且出错以后会检测到