Linux系统编程8–网络编程
基础知识
- 搜索结构体
r :递归 | i :不区分大小写 | n: 找出前置行号 |
---|
- 运行结果
0、回顾和引入
-
5种通信方式
- 管道、消息队列、共享内存、信号、信号量
- 特点:依赖于内核
- 必然缺陷:无法多机通信 (A、B 不以一个内核来管)
-
网络编程
-
地址
-
IP地址 & 端口号
-
IP地址负责将数据从源设备正确地传输到目标设备。 //找到PC
端口则确保在目标设备上,数据交付到正确的应用或服务。 //根据端口号找到应用/服务和通信协议
-
-
-
数据:协议(http、TCP/UDP)
- 数据格式
eg: 单片机->UART 51
- Socket套接字:TCP/UDP
TCP UDP 面向连接(打电话) 无连接(发短信) 面向字节流 面向报文(无拥塞控制) 点对点 一对一、一对多、多对一、多对多 全双工可靠信道(按序到达) 不可靠信道 (尽最大努力交付) 首部开销20字节 首部开销8字节 -
端口号的作用
-
1、字节序-大小端
-
字节序是指多字节数据在计算机内存中存储或者网络传输时各字节的存储顺序。
-
常见序
- Little endian:小端字节序(x86): 将低序位置(低位)存储在起始位置(低地址)
- Big endian: 大端字节序= 网络字节序 将高序位置(高位)存储在起始位置(低地址)·
0x01020304 //已存在数据
存储地址 | 小端字节序 | 大端字节序(网络) |
---|---|---|
4003 | 0000 0001(01) | 0000 0100(04) |
4002 | 0000 0010(02) | 0000 0011(03) |
4001 | 0000 0011(03) | 0000 0010(02) |
4000 | 0000 0100(04) | 0000 0001(01) |
2、socket编程介绍
- 客户端找服务器
2.1 socket服务器与客户端的开发步骤
2.2 Linux提供的API
2.2.1 创建套接字–socket()
- 指定讲“汉语”(连接协议)
int socket(int domain,int type,int protocol)
//创建套接字
//返回值:错误返回 -1
int socketz(AF_INET, SOCK_STREAM, 0); //IPv4、TCP协议
-
domain:
- 指明所使用的协议族,通常为AF_INET,表示互联网协议族(TCP/IP协议族)
- AF_INET IPv4因特网域
- AF_INET6 IPv6因特网域
- 指明所使用的协议族,通常为AF_INET,表示互联网协议族(TCP/IP协议族)
-
type:
- 指定socket的类型
- SOCK_STREAM: 使用TCP协议
- SOCK_DGRAM: 使用UDP协议
- 指定socket的类型
-
protocol:
- 通常赋值 “ 0 ”;
- 0 :选择type类型对应的默认协议
- IPPROTO_TCP TCP传输协议
- IPPROTO_UDP UDP传输协议
- IPPROTO_SCTP SCTP传输协议
- IPPROTO_TIPC TIPC传输协议
2.2.2 IP号、端口号与相应描述字赋值函数–bind()
- 地址准备好,告诉我的IP地址(楼号)& 端口号(房间号)
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
//用于绑定IP地址和端口号到socketfd
-
sockfd
:- 是一个
socket
描述符
- 是一个
-
addr
:-
是一个指向包含有本机IP地址及端口号等信息的
sockaddr
类型的指针 -
struct sockaddr{ unisgned short as_family; //协议族 char sa_data[14];//IP+端口 } //上面这种方式用的比较少 //同等替换 //一般用这种 struct sockaddr_in{ sa_family_t sin_family; /* 协议族 */ in_port_t sin_port; /* 端口号 */ struct in_addr sin_addr; /* IP地址结构体 */ unsigned char sin_zero[8]; /* 填充 没有实际意义,只是为了跟sockaddr结构在内存中对齐,以便相互转换 */ }
-
-
addrlem
:- 参数二,结构体的大小
sizeof
- 参数二,结构体的大小
2.2.3 地址转换API–inet_aton()&*inet_ntoa()
int inet_aton(const char *straddr,struct in_addr *addrp)
//把字符串形式的 ”192.168.1.123“转为网络能识别的格式
//参数一:字符串
//参数二:IP
char* inet_ntoa(struct in_addr inaddr);
//把网络格式的IP地址转为字符串形式
- 字节序转换
api
#include <stdio.h>
uint16_t htons(uint16_t host16bitvalue);//返回网络字节序的值
uint32_t htonl(uint32_t host32bitvalue);//返回网络字节序的值
uint16_t ntohs(uint16_t net16bitvalue);//返回主机字节序的值 uint32_t
ntohl(uint32_t net32bitvalue);//返回主机字节序的值
//h代表host
//n代表net
//s代表short (两个字节)
//l代表long(4个字节)
2.2.4 监听设置函数–listen()
#include <sys/types.h>
#include <sys/socket.h>
int listen(int sockfd, int backlog);
//sockfd:要监听的套接字
//backlog:支持的最大连接数
2.2.5 连接–accept()
//是否完成三次握手
#include <sys/types.h>
#include <sys/socket.h>
int accept(int socket, struct sockaddr *addr, socklen_t *addrlen);
//sockfd: 套接字,和文件描述符类似
//*addr:客户端地址
//*addrlen客户端地址的长度
//返回值:已创建好连接的套接字描述符
2.2.6 数据收发–write()&read()
– send()&recv()
//第一套
ssize_t write(int fd, const void *buf, size_t nbytes);
ssize_t read(int fd,void *buf, size_t nbyte);
//函数均返回读或写的字节个数,出错则返回 -1
//write将buf中的nbytes个字节写入到文件描述符fd中; 成功时返回写的字节数;
//read为从fd中读取nbyte个字节到buf中; 成功时返回实际所读的字节数
//第二套
//在TCP套接字上发送数据函数:有连接
ssize_t send(int s, const void *msg, size_t len, int flags);
//包含3要素:套接字s、待发数据msg、数据长度len
//函数只能对处于连接状态的套接字使用,参数s为 已建立好连接的套接字描述符,即accept函数的返回值
//msg:指向存放待发送数据的缓冲区
//len:为待发送数据的长度,
//flags:为控制选项,一般设置为0
//在TCP套接字上接收数据函数:有连接
ssize_t recv(int s, void *buf, size_t len, int flags);
//包含3要素:套接字s、接收缓冲区buf、数据长度len
//函数recv从参数s所指定的套接字描述符(必须是面向连接的套接字)上接收
//接收数据并保存到参数buf所指定的缓冲区
//len:为缓存区长度,参数flags为控制选项,一般设置为0
- UDP一般用
recvmsg()/sendmsg()、recvfrom()/sendto()
;
2.2.7 客户端连接主机–connect()
- 功能:该函数用于绑定之后的client端(客户端),与客户端建立连接
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
//sockfd:是目的服务器的socket描述符
//addr:是服务器端的IP地址和端口号的地址结构指针
//addrlen:地址长度常被设置为sizeof(struct sockaddr)
//返回值:成功返回0,遇到错误返回-1,并且errno中包含相应的错误码
3、socket服务端代码实现
3.1 服务端代码一
- PC机连接虚拟机【实验1】
//server.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
//#include <linux/in.h>
#include <arpa/inet.h>
#include <netinet/in.h> //该头文件会与 #include <linux/in.h> 冲突
#include <arpa/inet.h>
int main()
{
int s_fd;
//1、socket
s_fd = socket(AF_INET,SOCK_STREAM,0); //ivp4,TCP
if(s_fd == -1)
{
perror("socket");//将上一个函数错误的原因输出
exit(-1);
}
struct sockaddr_in s_addr;
s_addr.sin_family = AF_INET;//ivp4
s_addr.sin_port = htons(8888);//htons() 返回网络字节的端口号
inet_aton("192.168.1.85",&s_addr.sin_addr);//字符串类型形式的“192.0.0.1”转换为网络能识别的格式//字符串-指针
//2、bind
bind(s_fd, (struct sockaddr *)&s_addr, sizeof(struct sockaddr_in));
//3、listen
listen(s_fd,10);
//4、accept
int c_fd = accept(s_fd, NULL,NULL);
//5、read
//6、write
printf("connet\n");
while(1);
return 0;
}
- 如何验证呢?
1、先用在虚拟机里面用ifconfig
命令查看ip
地址
2、将**ip
地址和端口号**填写到server.c
里面
3、在终端运行server.c
程序阻塞在 accept函数
accept:是否完成三次握手
4、在window系统里面执行指令,使连接成功
5、如果不奏效,是因为在最新的 Windows 系统版本中,默认情况下是没有安装 Telnet 客户端的。需要手动启用 Telnet 客户端功能。
Telnet 也是TCP协议,所以可以连接上。
6、运行成功后,结果图
7、打开虚拟机,不再为阻塞状态,连接成功,输出提示符–connect
到这里就验证了实验一
3.2 服务端_客户端 代码二-双方消息收发
- 实验2
//server2.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h> //memset包含头文件
#include <sys/socket.h>
#include <sys/types.h>
//#include <linux/in.h>
#include <arpa/inet.h>
#include <netinet/in.h> //该头文件会与 #include <linux/in.h> 冲突
#include <arpa/inet.h>
#include <unistd.h>
int main(int argc, char **argv)
{
int s_fd;
int c_fd;
char readBuf[128];
char msg[128] ={0};
//sockaddr_in 是一个结构体,包含本机子ip地址和端口号等信息
struct sockaddr_in s_addr;//用于bind
struct sockaddr_in c_addr;//用于accept
if(argc != 3)
{
printf("paran is not good\n");
exit(-1);
}
memset(&s_addr,0,sizeof(struct sockaddr_in));//进行清空
memset(&c_addr,0,sizeof(struct sockaddr_in));
//1、socket
s_fd = socket(AF_INET,SOCK_STREAM,0); //ivp4,TCP
if(s_fd == -1)
{
perror("socket");//将上一个函数错误的原因输出
exit(-1);
}
s_addr.sin_family = AF_INET;//ivp4
s_addr.sin_port = htons(atoi(argv[2]));//htons() 返回网络字节的端口号 //argv[2]是第二个参数
inet_aton(argv[1],&s_addr.sin_addr);//字符串类型形式的“192.0.0.1”转换为网络能识别的格式//字符串-指针
//2、bind
bind(s_fd, (struct sockaddr *)&s_addr, sizeof(struct sockaddr_in));
//3、listen
listen(s_fd,10);
//4、accept
int clen = sizeof(struct sockaddr_in);
while(1)
{
c_fd = accept(s_fd, (struct sockaddr *)&c_addr,&clen);//accep第三个参数要求是指针//不连接,便堵塞,直到与客户端连接
if(c_fd == -1)
{
perror("accept");//终端打出错误:accept:---
}
printf("get connect: %s\n",inet_ntoa(c_addr.sin_addr));
if(fork() == 0)
{
if(fork()==0)
{
while(1)
{
memset(msg,0,sizeof(msg));
printf("input: ");
gets(msg);//不输出阻塞
write(c_fd,msg,strlen(msg));
}
}
//5、read
while(1)
{
memset(readBuf,0,sizeof(readBuf));
int n_read = read(c_fd,readBuf,128);
if(n_read == -1)
{
perror("read");
}
else
{
printf("get message:%d,%s\n",n_read,readBuf);
}
}
break;
}
}
return 0;
}
//client.c
//client.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h> //memset包含头文件
#include <sys/socket.h>
#include <sys/types.h>
//#include <linux/in.h>
#include <arpa/inet.h>
#include <netinet/in.h> //该头文件会与 #include <linux/in.h> 冲突
#include <arpa/inet.h>
#include <unistd.h>
int main(int argc, char **argv)
{
int c_fd;
char readBuf[128];
char msg[128] ={0};
//sockaddr_in 是一个结构体,包含本机子ip地址和端口号等信息
struct sockaddr_in c_addr;//用于accept
memset(&c_addr,0,sizeof(struct sockaddr_in));
if(argc != 3)
{
printf("param is not good\n");
exit(-1);
}
//1、socket
c_fd = socket(AF_INET,SOCK_STREAM,0); //ivp4,TCP
if(c_fd == -1)
{
perror("socket");//将上一个函数错误的原因输出
exit(-1);
}
c_addr.sin_family = AF_INET;//ivp4
c_addr.sin_port = htons(atoi(argv[2]));//htons() 返回网络字节的端口号
inet_aton(argv[1],&c_addr.sin_addr);//字符串类型形式的“192.0.0.1”转换为网络能识别的格式//字符串-指针
//2、connect
if(connect(c_fd,(struct sockaddr*)&c_addr,sizeof(struct sockaddr)) == -1)//不连接就阻塞
{
perror("connect");
exit(-1);
}
while(1)
{
if(fork()==0)
{
while(1)
{
printf("input: ");
memset(msg,0,sizeof(msg));
gets(msg);//不发的时候,阻塞
write(c_fd,msg,strlen(msg));
}
}
while(1)
{
memset(readBuf,0,sizeof(readBuf));//以免上面的数据保留
int n_read = read(c_fd,readBuf,128);
if(n_read == -1)
{
perror("read");
}
else
{
printf("get message from server:%d,%s\n",n_read,readBuf);
}
}
}
return 0;
}
- 实验2结果
- 发送88
- 发送888
3.3 服务端_客户端 代码三-多方消息收发
- 实验1
//server3.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h> //memset包含头文件
#include <sys/socket.h>
#include <sys/types.h>
//#include <linux/in.h>
#include <arpa/inet.h>
#include <netinet/in.h> //该头文件会与 #include <linux/in.h> 冲突
#include <arpa/inet.h>
#include <unistd.h>
int main(int argc, char **argv)
{
int s_fd;
int c_fd;
char readBuf[128];
int mark = 0;
char msg[128] ={0};
//sockaddr_in 是一个结构体,包含本机子ip地址和端口号等信息
struct sockaddr_in s_addr;//用于bind
struct sockaddr_in c_addr;//用于accept
if(argc != 3)
{
printf("paran is not good\n");
exit(-1);
}
memset(&s_addr,0,sizeof(struct sockaddr_in));//进行清空
memset(&c_addr,0,sizeof(struct sockaddr_in));
//1、socket
s_fd = socket(AF_INET,SOCK_STREAM,0); //ivp4,TCP
if(s_fd == -1)
{
perror("socket");//将上一个函数错误的原因输出
exit(-1);
}
s_addr.sin_family = AF_INET;//ivp4
s_addr.sin_port = htons(atoi(argv[2]));//htons() 返回网络字节的端口号 //argv[2]是第二个参数
inet_aton(argv[1],&s_addr.sin_addr);//字符串类型形式的“192.0.0.1”转换为网络能识别的格式//字符串-指针
//2、bind
bind(s_fd, (struct sockaddr *)&s_addr, sizeof(struct sockaddr_in));
//3、listen
listen(s_fd,10);
//4、accept
int clen = sizeof(struct sockaddr_in);
while(1)
{
c_fd = accept(s_fd, (struct sockaddr *)&c_addr,&clen);//accep第三个参数要求是指针//不连接,便堵塞,直到与客户端连接
if(c_fd == -1)
{
perror("accept");//终端打出错误:accept:---
}
mark++;
printf("get connect: %s\n",inet_ntoa(c_addr.sin_addr));
if(fork() == 0)
{
if(fork()==0)//再次创建线程
{
while(1)
{
sprintf(msg,"welcom No %d client",mark);
write(c_fd,msg,strlen(msg));
sleep(10);
}
}
//5、read
while(1)
{
memset(readBuf,0,sizeof(readBuf));
int n_read = read(c_fd,readBuf,128);
if(n_read == -1)
{
perror("read");
}
else
{
printf("get message:%d,%s\n",n_read,readBuf);
}
}
break;
}
}
return 0;
}
-
实验结果
- 实现了多个客户端给服务器发信息的功能
-
实验结果
-
多个客户端与服务器建立连接
-
客户端发送数据给服务器
-
服务器只是一个中转站,负责消息的周转、不负责与客户端不断地交互发送消息,而是负责管理客户端的关系,以及客户端注册关系,可以把客户端消息记录在数据库中;
-
客户端与客户端进行通信
欢迎大家一起交流讨论!