网络编程
TCP
创建服务端
创建一个socket文件描述符
int socket(int domain, int type, int protocol);
//参数1:协议族(ipv4是AF_INET等)
//参数2:套接字类型(TCP->流式套接字,UDP->数据报套接字)
//参数3:0
//返回值:套接字描述符,-1
绑定套接字(ip+port)
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
//参数1:socket描述符
//参数2: man 7 ip 强转 (struct sockaddr *)
//参数3:大小
//返回值:0,-1
//参数2: man 7 ip
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 */
};
//协议族 ipv4
//端口号 转成网络字节序 htons()
//ip地址
/* Internet address. */
struct in_addr {
uint32_t s_addr; /* address in network byte order */
};
//ip转换函数
inet_addr()//把192.xxx转换为32位的(人看的 ->机器)
inet_ntoa()//(机器 ->人)
启动监听
int listen(int sockfd, int backlog);
//参数1: socket描述符
//参数2: 监听队列最大长度
//返回值;0,-1
接收客户端请求(阻塞接口)
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
//参数1:socket描述符
//参数2:客户端网络结构体的指针(对方地址)
//参数3:客户端网络结构体的大小指针
//返回值;已连接新的客户端的文件描述符,-1
示例代码
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{
int sockfd; // 定义一个套接字的描述符
if (argc < 3)
{
printf("usage : %s <ip> <port>\n", argv[0]);
return -1;
}
// 创建一个socket文件描述符
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
perror("sock err\n");
return -1;
}
// 绑定套接字(ip+port)
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(atoi(argv[2])); // 端口号 htons (主机序->网络序)
#if 0
addr.sin_addr.s_addr = inet_addr(argv[1]);
#else
addr.sin_addr.s_addr = INADDR_ANY; // 自动绑定本机所有网卡地址
#endif
if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
{
perror("bind err");
return -1;
}
// 启动监听
if (listen(sockfd, 5) < 0)
{
perror("lisetn err\n");
return -1;
}
// 接收客户端请求(阻塞接口)
printf("wait client connect\n");
int clifd; // 返回已连接新的客户端的文件描述符,后续读写都要用这个新的进行
//客户端地址(传入的,不赋值)
struct sockaddr_in cliaddr;
int cli_addrlen = sizeof(cliaddr);
clifd = accept(sockfd, (struct sockaddr*)&cliaddr, &cli_addrlen);
if (clifd < 0)
{
perror("accept err\n");
return -1;
}
else
{
printf("new connect successful\n");
printf ("ip = %s, port = %d\n",inet_ntoa(cliaddr.sin_addr),ntohs(cliaddr.sin_port));
}
// 读写
#define N 64
int ret;
char buf[N] = {0};
while (1)
{
ret = recv(clifd, buf, N, 0);
if (ret < 0)
{
perror("recv err\n"); // 接收失败,继续
continue;
}
else if (ret > 0)
{
printf("recive data = %s\n", buf); // 打印接收的字符
}
else // ret =0
{
printf("peer quit\n"); // 对方挂掉,退出
break;
}
}
close(clifd);
close(sockfd);
return 0;
}
创建客户端
在服务器中,0.0.0.0指的是本机上的所有IPV4地址,如果一个主机有两个IP地址,192.168.1.1 和 10.1.2.1,并且该主机上的一个服务监听的地址是0.0.0.0,那么通过两个ip地址都能够访问该服务。
127.0.0.1属于{127,}集合中的一个,而所有网络号为127的地址都被称之为回环地址,所以回环地址!=127.0.0.1,它们是包含关系,即回环地址包含127.0.0.1。
回环地址:所有发往该类地址的数据包都应该被loop back。
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
//参数1:
//参数2:
//参数3:
//返回值0,-1
代码
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char const *argv[])
{
// 创建套接字
int sockfd; // 定义一个套接字的描述符
if (argc < 3)
{
printf("usage : %s <ip> <port>\n", argv[0]);
return -1;
}
// 创建一个socket文件描述符
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
perror("sock err\n");
return -1;
}
// 请求连接
// 指定服务器地址
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(atoi(argv[2])); // 端口号 htons (主机序->网络序)
addr.sin_addr.s_addr = inet_addr(argv[1]);
if (connect(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
{
perror("connect err\n");
return -1;
}
printf("connect sucessful\n");
#define N 64
char buf[N] = {0};
while (1)
{
gets(buf);
if (strcmp(buf, "quit") == 0)
{
break;
}
send(sockfd, buf, N, 0);
}
close(sockfd);
return 0;
}
FTP传输文件
客户端:连接成功 打开文件 循环读写文件 文件数据帧发送到服务器 当读完退出循环 关闭
服务端:收到客户端连接 打开文件(清空 创建) 循环写入数据帧 recv() == 0//客户端退出
客户端
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
// 创建套接字
int sockfd; // 定义一个套接字的描述符
if (argc < 4)
{
printf("usage : %s <ip> <port> <file>\n", argv[0]);
return -1;
}
// 创建一个socket文件描述符
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
perror("sock err\n");
return -1;
}
// 文件的创建打开
int fd;
fd = open(argv[3], O_RDONLY);
if (fd < 0)
{
perror("open err\n");
return -1;
}
// 请求连接
// 指定服务器地址
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(atoi(argv[2])); // 端口号 htons (主机序->网络序)
addr.sin_addr.s_addr = inet_addr(argv[1]);
if (connect(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
{
perror("connect err\n");
return -1;
}
printf("connect sucessful\n");
#define N 64
char buf[N] = {0};
while (1)
{
// 读取文件
int ret;
ret = read(fd, buf, N);
if (ret < 0)
{
perror("read err\n");
break;
}
else if (ret > 0)
{
send(sockfd, buf, ret, 0); // 按照实际读到的字节数发送
}
else
//文件读取完成,退出循环
{
break;
}
// gets(buf);
// if (strcmp(buf, "quit") == 0)
// {
// break;
// }
}
close(sockfd);
return 0;
}
服务端
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char const *argv[])
{
int sockfd; // 定义一个套接字的描述符
if (argc < 3)
{
printf("usage : %s <ip> <port>\n", argv[0]);
return -1;
}
// 创建一个socket文件描述符
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
perror("sock err\n");
return -1;
}
// 绑定套接字(ip+port)
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(atoi(argv[2])); // 端口号 htons (主机序->网络序)
#if 0
addr.sin_addr.s_addr = inet_addr(argv[1]);
#else
addr.sin_addr.s_addr = INADDR_ANY; // 自动绑定本机所有网卡地址
#endif
if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
{
perror("bind err");
return -1;
}
// 启动监听
if (listen(sockfd, 5) < 0)
{
perror("lisetn err\n");
return -1;
}
// 接收客户端请求(阻塞接口)
printf("wait client connect\n");
int clifd; // 返回已连接新的客户端的文件描述符,后续读写都要用这个新的进行
// 客户端地址(传入的,不赋值)
struct sockaddr_in cliaddr;
int cli_addrlen = sizeof(cliaddr);
clifd = accept(sockfd, (struct sockaddr *)&cliaddr, &cli_addrlen);
if (clifd < 0)
{
perror("accept err\n");
return -1;
}
else
{
printf("new connect successful\n");
printf("ip = %s, port = %d\n", inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port));
}
int fd;//没有则创建
fd = open("new.txt", O_WRONLY | O_CREAT | O_TRUNC,0666);
if (fd < 0)
{
perror("open err\n");
return -1;
}
// 读写
#define N 64
int ret;
char buf[N] = {0};
while (1)
{
ret = recv(clifd, buf, N, 0);
if (ret < 0)
{
perror("recv err\n"); // 接收失败,继续
continue;
}
else if (ret > 0)
{
write(fd ,buf,ret);
// printf("recive data = %s\n", buf); // 打印接收的字符
}
else // ret =0
{
printf("file recv sucessful\n"); // 对方挂掉,退出
break;
}
}
close(clifd);
close(sockfd);
return 0;
}
UDP
区别
同: 同属传输层协议
tcp 面向连接 可靠 (流式套接字)
udp 无连接 不可靠 (数据报套接字)
client.c
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char const *argv[])
{
// 创建套接字
int sockfd; // 定义一个套接字的描述符
if (argc < 3)
{
printf("usage : %s <ip> <port>\n", argv[0]);
return -1;
}
// 创建一个socket文件描述符
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0)
{
perror("sock err\n");
return -1;
}
// 请求连接
// 指定服务器地址
struct sockaddr_in srvaddr;
srvaddr.sin_family = AF_INET;
srvaddr.sin_port = htons(atoi(argv[2])); // 端口号 htons (主机序->网络序)
srvaddr.sin_addr.s_addr = inet_addr(argv[1]);
int addrlen = sizeof(srvaddr);
if (connect(sockfd, (struct sockaddr *)&srvaddr, sizeof(srvaddr)) < 0)
{
perror("connect err\n");
return -1;
}
printf("connect sucessful\n");
ssize_t len;
#define N 64
char buf[N] = {0};
while (1)
{
memset(buf, N, 0);
fgets(buf, N, stdin);
sendto(sockfd, buf, N, 0, (struct sockaddr *)&srvaddr, addrlen);
len = recvfrom(sockfd, buf, N, 0, NULL, NULL);
if (len > 0)
{
printf("recv data = %s\n", buf);
}
}
return 0;
}
server.c
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char const *argv[])
{
int sockfd; // 定义一个套接字的描述符
if (argc < 3)
{
printf("usage : %s <ip> <port>\n", argv[0]);
return -1;
}
// 创建一个socket文件描述符
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0)
{
perror("sock err\n");
return -1;
}
// 绑定套接字(ip+port)
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(atoi(argv[2])); // 端口号 htons (主机序->网络序)
#if 0
addr.sin_addr.s_addr = inet_addr(argv[1]);
#else
addr.sin_addr.s_addr = INADDR_ANY; // 自动绑定本机所有网卡地址
#endif
int addrlen = sizeof(addr);
if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
{
perror("bind err");
return -1;
}
printf("wait connect\n");
// 接收
ssize_t len;
#define N 64
char buf[N] = {0};
struct sockaddr_in cliaddr; // 接收客户端的地址
while (1)
{
memset(buf,N,0);
len = recvfrom(sockfd, buf, N, 0, (struct sockaddr *)&cliaddr, &addrlen);
if (len > 0)
{
printf("recv data = %s\n", buf);
sendto(sockfd, buf, N, 0, (struct sockaddr *)&cliaddr, addrlen); // 原样返回
}
}
return 0;
}
查看端口占用 然后kill掉
netstat -ntlp
IO模型
阻塞模型:不占用CPU,但是不能同时处理多个设备
非阻塞模型:能同时处理多个设备,但是非常耗费CPU
异步IO/信号驱动IO:完全解放了主线程,有数据会主动通知,异步调用处理方法,但是也不
能同时处理多个设备。
IO多路复用:既能同时处理多个设备,又不占用CPU
以上这4种方法没有优劣之分,在不同的场合使用不同的方法。
阻塞不占用cpu资源
while (1)
{
memset(buf, N, 0);
gets(buf);//不占cpu
printf("buf = %s\n", buf);
}
修改
#if 1
int flag; // 文件状态标志
flag = fcntl(fd, F_GETFL); // 读
flag |= O_NONBLOCK; // 改 O_NONBLOCK = 0x00004000(非阻塞)
fcntl(fd, F_SETFL, flag); // 写
#endif
置位 清零
1那个位
|= 000001
& ~(00001)
非阻塞
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#define N 64
char buf[N] = {0};
int main(int argc, char const *argv[])
{
//1 非阻塞 0 阻塞
#if 1
int flag; // 文件状态标志
flag = fcntl(0, F_GETFL); // 读 fd = 0;标准输入
flag |= O_NONBLOCK; // 改 O_NONBLOCK = 0x00004000(非阻塞)
fcntl(0, F_SETFL, flag); // 写
#endif
while (1)
{
memset(buf, N, 0);
gets(buf);
printf("buf = %s\n", buf);
sleep(1);
}
return 0;
}
异步IO模型
以16进制打印设备数据 hexdump
hexdump /dev/input/mouse0
查看信号
kill -l
获取鼠标数据长度(异步 占用cpu不高)
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
static int fd;
#define N 64
// 调用这个方法的时候,一定是SIGIO信号被触发,一定有数据
void handler(int signum)
{
char buf[N] = {0};
int ret = read(fd, buf, N);
if (ret < 0)
{
perror("read err\n");
return;
}
else
{
printf("len = %d\n", ret);
}
}
// 从鼠标获取数据,打印
int main(int argc, char const *argv[])
{
fd = open("/dev/input/mouse0", O_RDONLY);
if (fd < 0)
{
perror("open err\n");
return -1;
}
#if 1
// 开启异步通知
fcntl(fd, F_SETOWN, getpid());
// 使能异步通知
int flag;
flag = fcntl(fd, F_GETFL);
flag |= FASYNC;
fcntl(fd, F_SETFL, flag);
signal(SIGIO, handler);
#endif
while (1)
{
sleep(1);
}
return 0;
}
IO多路复用
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/select.h>
/**
* 同时监听标准输入、鼠标
* 如果两个设备都没有数据,那么进程睡眠
* 如果任意一个有数据,那么读取其数据
* */
#define MOUSE "/dev/input/mouse0"
#define N 64
char buf[N] = {0};
int main(int argc, char const *argv[])
{
// 打开鼠标设备
int fd;
fd = open(MOUSE, O_RDONLY);
if (fd < 0)
{
perror("open err\n");
return -1;
}
// 定义一张描述符表
fd_set rdfds;
int maxfd = fd; // fd一定比标准输入大
int ret;
while (1)
{
//每次会修改表,所以在select前需要重新设置
// 设置描述符表,增加标准输入与鼠标的监听
FD_ZERO(&rdfds); // 清空表
FD_SET(0, &rdfds); // 把标准输入增加到表,0是标准输入
FD_SET(fd, &rdfds); // 鼠标
// 如果监听设备无数据,select会阻塞等待
ret = select(maxfd + 1, &rdfds, NULL, NULL, NULL);
if (ret < 0)
{
perror("select err\n");
continue;
}
else if (ret > 0) // 某一个设备有数据了
{
if (FD_ISSET(0, &rdfds)) // 如果是标准输入
{
gets(buf); // 这里一定不会阻塞,因为一定有数据了
printf("buf = %s\n", buf);
}
if (FD_ISSET(fd, &rdfds)) // 如果是鼠标
{
int len = read(fd, buf, N);
printf("MOUSE len = %d\n", len);
}
}
else
{
}
}
return 0;
}
select、poll和epoll
select
一个进程最多只能监听1024个文件描述符(千级别)
select每次会清空表,每次都需要拷贝用户空间的表到内核空间,效率低
poll
优化文件描述符个数的限制根据poll函数第一个函数的参数来定,如果监听的事件为1个,
则结构体数组的大小为1,如果想监听100个,那么这个结构体数组的大小就为100,由程序员
自己来决定)
·poll不需要重新构造文件描述符表,只需要从用户空间向内核空间拷贝一次数据即可
epoll(了解即可)epoll详解
监听的最大的文件描述符没有个数限制
(理论上,取决与你自己的系统
·异步I/O,Epoll当有事件产生被唤醒之后,文件描述符主动调用callback(回调函数)函数直接
拿到唤醒的文件描述符,不需要轮询,效率极高
理论
网络模型
区别
字节序
小端序(little-endian)·低序字节存储在低地址
大端序(big-endian)·高序字节存储在低地址
网络中传输的数据必须使用网络字节序,即大端字节序
写一个函数,判断当前主机的字节序?
主机字节序到网络字节序
网络字节序到主机字节序
IP地址转换
TCP编程
UDP
三次握手和四次挥手
网络超时检测
利用select/poll超时特性
如果使用select模型,最后一个参数可以直接实现超时检测动作。