网络编程
一·网络编程
进行多级通信的时候首先要了解双方的地址 和要建立的连接协议
需要了解IP地址 和端口号
数据
协议 http TCP/UDP 协议
socket套接字
TCP 面向连接的 A B 打电话 可靠
UDP 面向报文的 A B 发短信 不可靠 数据量大
1、TCP面向连接(比如打电话那种连接起来);UDP是无连接的,及发送数据前不需要提前建立连接。
2、TCP提供可靠的服务,也就是说,通过TCP连接的数据,无差错,不丢包,不重复,且按序到达,UDP尽最大努力交付,也不能保证可靠交付
3、TCP面向字节流,实际上是TPC把数据看成一连串无结构的字节流
UDP是面向报文的,UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低
4、每一条TCP连接只能是点到点的 UDP支持一对一,一对多,多对一和多对多的交互通信
5、TCP首部开销20字节 UDP的首部开销小,只有八个字节
6、TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道
字节序
STM32的堆栈特点 小端模式 高字节在高地址 高字节表示的是前面的字节
字节序是指多字节数据在计算机内存中储存或者网络传输时,各字节的储存序列
小端字节序 将低序字节储存在起始位置
大端字节序 将高序字节储存在起始位置
使用小端模式就行储存的话高字节在高地址
04 表示的是低序
01 表示的是高序
重要提醒
网络字节序是大端模式 而计算机上使用的是小端模式 因此需要进行大端模式到小端模式的转变
uint16_t htons(); //将端口号转换成网络字节序
socket 编程步骤
我是服务器
我说的是汉语(TCP UDP)
我的IP地址是(楼房号)
我的端口号是(房间号)
我在监听(等待大家来访,来了敲门)
作为客户端
我就知道了 IP地址 获取了服务器的ip地址
获取了服务器的端口号
就可以连接服务器了(进入房间)
(1)服务端 开发步骤
1、创建套接字 sockat() 生成s_fd
2、为套接字添加信息(IP地址及端口号) bind() 使用s_fd
3、监听网络连接 listen() 使用s_fd
4、监听到有客户端接入,接受一个连接 accept() 生成c_fd
5、数据交互 read write 使用c_fd
6、关闭套接字,关闭连接
(2)客户端 开发步骤
1.创建套接字 socket() 生成s_fd
2.连接服务器端 connect() 使用s_fd
3.进行数据交互 read write 使用s_fd
4.关闭套接字
1.指定讲汉语 TCP/UDP (连接协议)
socket函数原型
int socket(int domain, int type, int protocol);
//通常写0
s_fd=socket(AF_INET,SOCK_STREAM,0);
因此函数的使用 第一个参数使用的是AF_INET 表示互联网协议(TCP/UDP)
第二个参数使用的是SOCK_STREAM 表示的是TCP连接
第三个参数 默认写0 表示通过系统进行配置
2.准备好地址 IP地址 端口号
bind()函数原型`
int bind(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
s_addr.sin_family=AF_INET; // 结构体使用
s_addr.sin_port=htons(8888); //使用htons转换端口号
inet_aton("192.168.43.14",&s_addr.sin_addr);//把IP地址转换为可识别的
bind(s_soc,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
//强制转换成struct sockaddr *
第一个参数是socket的描述符
第二个参数是指向包含本机地址ip号地址的端口号等信息的sockaddr类型的指针 我们使用的结构体类型是sockaddr_in 因此在配置函数的时候需要进行类型的强制转换
第三个参数是 结构体长度
端口号一般是 5000 到9000 之间
结构体的第一个参数 协议族 就是socket的第一个参数
我们要使用的结构体不在该头文件下面 因此我们要使用的话需要进行头文件的查找 进入到头文件的目录下面
cd /usr/include
grep “struct sockaddr_in {” * -nir n表示行号 i 表示不区分大小写 r 定位
*表示在当前目录下
vi linux/in.h +184 表示进入linux/in.h 的头文件 +184 表示定位在184 行
3.网络地址转换 API
我们将写入的字符串(IP地址)发送到网络上面的时候网络上面会不认识这个模式 因此我们需要进行转换 变成网络认识的格式之后再进行创建和连接
int inet_aton(const char *cp, struct in_addr *inp);
//这里是* 要取地址
把字符串形式的"192.168.43.14"转换成网络可以认识的网络格式
使用方法
inet_aton("192.168.43.14",&addr_in.sin_addr);
使用该函数需要 取地址 原型中
将字符串转换成要发送给网络的IP地址
char *inet_ntoa(struct in_addr in);
把得到的网络符转换成字符串
printf("get id %s \n",inet_ntoa(c_addr.sin_addr));
把网络格式转换成字符串
4.监听
只能用于服务端 不能用于接受端
int listen(int sockfd, int backlog);
listen(s_soc,10); //监听十次
第一个参数socket返回的值 sockfd
第二个参数表示请求队列中最大的允许请求数
5.连接
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
struct sockaddr_in c_addr;
int clen=sizeof(struct sockaddr_in);
int c_fd=accept(s_soc,(struct sockaddr *)&c_addr,&clen);
printf("get id %s \n",inet_ntoa(c_addr.sin_addr));
//打印接受到的网络字节序转换成字符串
accept函数是由TCP服务器调用,用于已完成连接的队列对头返回下一个已经完成的连接,如果已连接队列为空,那么进程将进入休眠状态
6.数据收发
7.客户端的connect函数
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
addr 服务器端ip地址和端口号的结构指针
addrlen 长度 通常设置为 seziof(struct sockaddr);
if(connect(c_fd,(struct sockaddr*)&c_addr,sizeof(struct sockaddr))==-1)
返回值: 成功返回值是0 错误返回值是-1 并且errno中存在错误信息
8.字节序转换
addr_in.sin_port=htons(8888); //字节序转换端口号
s_addr.sin_port=htons(atoi(argv[2])); //使用htons转换端口号
因为前面使用的是char *argv[ ] 因此我们需要把外面写入的字符串转换成整型数进行转换 因此用到了atoi
9. 服务器端例子
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
int main()
{
int s_soc;
s_soc=socket(AF_INET,SOCK_STREAM,0);
if(s_soc==-1){
perror("socket");
exit(-1);
}
int nread;
char readBuf[128]={0};
char *returnBuf="I get message!";
struct sockaddr_in addr_in;
struct sockaddr_in c_addr;
memset(&c_addr,0,sizeof(struct sockaddr_in));
memset(&addr_in,0,sizeof(struct sockaddr_in));
int clen=sizeof(struct sockaddr_in);
//2.bind
addr_in.sin_family=AF_INET; // 结构体使用
addr_in.sin_port=htons(8888); //字节序转换端口号
inet_aton("192.168.43.14",&addr_in.sin_addr);//把IP地址转换为可识别的
bind(s_soc,(struct sockaddr *)&addr_in,sizeof(struct sockaddr_in));
//3.listen
listen(s_soc,10);
//4.accept
int c_fd=accept(s_soc,(struct sockaddr *)&c_addr,&clen);
printf("get id %s \n",inet_ntoa(c_addr.sin_addr));
//5.read
nread= read(c_fd,readBuf,128);
if(nread==-1){
perror("read");
}else{
printf("get messge : %d %s \n",nread,readBuf);
}
//6.write
write(c_fd,returnBuf,strlen(returnBuf));
return 0;
}
10.客户端例子
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
int main()
{
//1.socket
int c_fd;
c_fd=socket(AF_INET,SOCK_STREAM,0);
if(c_fd==-1){
perror("socket");
exit(-1);
}
int nread;
char readBuf[128]={0};
char *returnBuf="I get !";
struct sockaddr_in c_addr;
memset(&c_addr,0,sizeof(struct sockaddr_in));
int clen=sizeof(struct sockaddr_in);
//2.connect
c_addr.sin_family=AF_INET;
c_addr.sin_port=htons(8888);
inet_aton("192.168.43.14",&c_addr.sin_addr);
//使用连接函数 进行连接
if(connect(c_fd,(struct sockaddr*)&c_addr,sizeof(struct sockaddr))==-1){
perror("connect");
exit(-1);
}
printf("get id %s \n",inet_ntoa(c_addr.sin_addr));
//3.send
write(c_fd,returnBuf,strlen(returnBuf));
//4.read
nread= read(c_fd,readBuf,128);
if(nread==-1){
perror("read");
}else{
printf("get messge connect: %d %s \n",nread,readBuf);
}
return 0;
}
11.服务器端对应多个客户端
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
int main(int argr,char *argv[])
{
if(argr!=3){
perror("why");
exit(1);
}
int s_fd;
int c_fd;
int nread;
int mark=0;
char readBuf[128]={0};
char returnBuf[128]={0};
struct sockaddr_in s_addr;
struct sockaddr_in c_addr;
int clen=sizeof(struct sockaddr_in);
//1.socket
s_fd=socket(AF_INET,SOCK_STREAM,0);
if(s_fd==-1){
perror("socket");
exit(1);
}
s_addr.sin_family=AF_INET; // 结构体使用
s_addr.sin_port=htons(atoi(argv[2])); //使用htons转换端口号
inet_aton(argv[1],&s_addr.sin_addr);//把IP地址转换为可识别的
//2.bind
bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
//3.listen
listen(s_fd,10);
// 4.accept
while(1){
mark++; //使用mark记录是第几个接入
c_fd=accept(s_fd,(struct sockaddr *)&c_addr,&clen);
printf("get id %s \n",inet_ntoa(c_addr.sin_addr));
//5.read
if(fork()==0){ //使用子进程,让前面的mark和子进程同时进行
if(fork()==0){//使用fork 让写和读同时进行
while(1){
//可以读取多个客户端
nread=read(c_fd,readBuf,128);
if(nread==-1){
perror("why");
}else{
printf("read %d content %s\n",nread,readBuf);
}
}
}
//6.write
while(1){
sprintf(returnBuf,"The N.%2d\n",mark);
write(c_fd,returnBuf,strlen(returnBuf));
sleep(4); //进行延时保证所有的客户端都可以收到
}
}
}
return 0;
}
12.可创建多个客户端
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
int main(int argr,char *argv[])
{
if(argr!=3){
perror("why");
exit(1);
}
int c_fd;
int nread;
char readBuf[128]={0};
char returnBuf[128]={0};
struct sockaddr_in s_addr;
struct sockaddr_in c_addr;
int clen=sizeof(struct sockaddr_in);
//1.socket
c_fd=socket(AF_INET,SOCK_STREAM,0);
if(c_fd==-1){
perror("socket");
exit(1);
}
c_addr.sin_family=AF_INET; // 结构体使用
c_addr.sin_port=htons(atoi(argv[2])); //使用htons转换端口号
inet_aton(argv[1],&c_addr.sin_addr);//把IP地址转换为可识别的
//2.connect
if(connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr))){
perror("why connect ");
exit(1);
}
while(1){
//在while语句前面创建进程 创建成功后会在程序中不断执行该进程 并且不影响下面的while循环
if(fork()==0){//这里只有两个写和读两个同时进行的进程
while(1){
gets(returnBuf);
write(c_fd,returnBuf,strlen(returnBuf));
}
}
//5.read
while(1){
nread=read(c_fd,readBuf,128);
if(nread==-1){
perror("why");
}else{
printf("read %d content %s\n",nread,readBuf);
}
}
}
return 0;
}
总结
一个进程执行一个不同的程序 在客户端函数 希望客户端可以同时执行 读操作和写操作 因此 我们需要进行创建一个进程进行读操作 或者写操作 剩下的那个进行剩下的操作
使用while循环进行操作 如果在 while循环后面创建进程函数就不会创建成功 相当于只执行一个操作
因此 进程的函数写在while函数的前面 先生成一个进程操作 然后在执行while循环 相当于可以同时执行两个不同的操作
使用 进程进行操作 执行写操作的时候 需要在进程里面使用while循环 这样才能保证每次都可以发送给 客户端
使用telnet 进行连接 telnet 192.168.8.129 8888 连接服务器端
杀死端口进程
fuser -n tcp -k 8889 使用该指令可以杀死该端口号的进程
杀死进程占用的端口号 首先使用netstat -antp |grep (端口号)
然后看到他的端口号对应的进程号杀死
-
建立的socket连接 在服务器端通过accept创建的描述符进行发送和接受的操作
而客户端是通过socket创建的描述符 进行发送和接收 -
创建的socket服务 socket发送给服务器信息的顺序是通过连接的循序 第一次发送信息发送给第一个连接的 第二次发送给第二个 以此类推
-
当有设备连接进来的时候不会影响发送的顺序 比如当前有三个客户端 给1发送完数据 当又有一个接入进来 会把它作为第四个 2,3,4 后再1,2,3,4传递参数。 当中途有设备退出后 还会按照设备一次发送 默认当前设备没有退出。