网络编程(二)

  • 基于TCP协议的网络客户端
    • 服务端
      • **socket**:创建网络软通道
      • bind**:给socket套接字绑定网络终端主机信息
      • **listen**:监听有没有客户端接入
      • **accept**:接入连接(产生一个新的socket文件描述符)
      • **recv**:接收数据
      • **send**:发送数据
    • 客户端
      • **connect**:连服务端
  • TCP通信中存在的数据传递现象(粘包)
    • 粘包是什么?
    • 如何避免粘包?

基于TCP协议的网络客户端

网络编程(二)插图

服务端

步骤:

  1. socket(创建套接字)
  2. bind(绑定本机地址和端口)
  3. listen(侦听)
  4. accept(接听)
  5. IO函数(发送数据)(read/write recv/send)

网络编程(二)插图(1)

socket:创建网络软通道

#include
#include
int socket(int domain, int type, int protocol);
//参数1:协议域(AF_INET)ipv4
//参数2:套接字类型–>SOCK_STREAM 流式套接字
//参数3:其他协议–>0:自动匹配其他需要的协议
//返回值:成功返回文件描述符,标识socket网络软通道;失败返回-1,更新errno

bind**:给socket套接字绑定网络终端主机信息

#include
#include
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
//参数1:文件描述符–>socket返回值
//参数2:指向网络终端主机信息的结构体(协议域,IP地址,端口号)
//参数3:struct sockaddr的大小
//返回值:成功返回0,失败返回-1,更新errno
//原结构体
struct sockaddr {
sa_family_t sa_family;
char sa_data[14];
};
//替换的结构体
struct sockaddr_in{
sa_family_t sin_family;
in_port_t sin_port;
struct in_adrr sin_addr;
};
struct in_addr{
__bs32 s_addr;
}
该结构体总共占16字节,两个结构体可以强转的前提是所占空间大小相同,借用struct sockaddr_in结构体存储,之后强转为struct sockaddr

网络编程(二)插图(2)
网络编程(二)插图(3)

网络编程(二)插图(4)
inet_addr():将点分十进制IP地址转为32位无符号整数

#include 
#include 
#include 
in_addr_t inet_addr(const char *cp);

网络编程(二)插图(5)

listen:监听有没有客户端接入

#include
#include
int listen(int sockfd, int backlog);
//参数1:文件描述符–>socket返回值
//参数2:最大监听客户端的个数
//返回值:成功返回0;失败返回-1,并更新errno

accept:接入连接(产生一个新的socket文件描述符)

#include
#include
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
//参数1:文件描述符–>socket返回值
//参数2:指向存放对方的主机信息结构体
//参数3:指向struct sockaddr的大小的指针
//返回值:成功返回新的文件描述符,标识一个新的网络软通道,用作收发正文数据(recv/send);失败返回-1,并更新errno

recv:接收数据

#include
#include
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
//参数1:文件描述符–>accept的返回值
//参数2:用于存放接收数据的BUF
//参数3:期望接收数据的大小
//参数4:阻塞&非阻塞的标志 0–>阻塞
//返回值:成功返回接收的字节数;失败返回-1,并更新errno

send:发送数据

#include
#include
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
//参数1:文件描述符–>accept的返回值
//参数2:用于存放接收数据的BUF
//参数3:期望发送数据的大小
//参数4:阻塞&非阻塞的标志 0–>阻塞
//返回值:大于0,发送的字节个数;发送失败返回-1,并更新errno

server:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define BUF_SIZE 20
//tcpserver
int main()
{
//socket
int serverfd = socket(AF_INET, SOCK_STREAM,0);
//判断socket返回值
if(-1 == serverfd)
{
perror("socket error");
return -1;
}
//创建软通道成功
printf("socket ok------\r
");
//bind
struct sockaddr_in stserver;
stserver.sin_family = AF_INET;
stserver.sin_port = htons(8888);
stserver.sin_addr.s_addr = inet_addr("127.0.0.1");
int ret = bind(serverfd, (struct sockaddr*)&stserver, sizeof(struct sockaddr));
//判断的返回值
if(-1 == ret)
{
//失败打印失败原因并返回
perror("bind error");
return -1;
}
//绑定服务端主机成功
printf("bind ok------\r
");
//listen
ret = listen(serverfd, 5);
//返回值判断
if(-1 == ret)
{
//失败打印失败原因并返回
perror("listen error");
return -1;
}
//监听创建成功
printf("listen ok------\r
");
//接收客户端的信息
struct sockaddr_in stclient;
//结构体大小
socklen_t len = sizeof(struct sockaddr);
//创建收发数据的缓冲区
char buf[BUF_SIZE] = {0};
while(1)
{
//清空接收数据的缓冲区
memset(buf, 0, BUF_SIZE);
//accept
int newfd = accept(serverfd, (struct sockaddr*)&stclient, &len);
//返回值为为客户端创建新的软通道
if(-1 == newfd)
{
//创建失败,进行下一次侦听接收
perror("accept error");
continue;
}
//连接成功并软通道创建成功
printf("accept ok-----\r
");
//recv/send
ret = recv(newfd, buf, BUF_SIZE, 0);
if(ret <= 0)
{
//失败返回原因并返回
perror("recv error or recive end");
close(newfd);
continue;
}
printf("recive data:%s\r
", buf);
memset(buf, 0, BUF_SIZE);
printf("please data:\r
");
fgets(buf, BUF_SIZE, stdin);
//send为发送数据
ret = send(newfd, buf, BUF_SIZE, 0);
//返回值判断
if(ret <= 0)
{
//失败返回原因,不做返回,关闭这次软通道,接收下一个客户端
perror("send error or send end");
close(newfd);
continue;
}
//关闭新建软通道描述符
close(newfd);
}
return 0;
}

客户端

步骤:

  1. socket
  2. connect(连接)
  3. IO函数(write/ read recv/send)
    网络编程(二)插图(6)

connect:连服务端

#include
#include
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
//参数1:文件描述符–>socket返回值
//参数2:用于存放服务端的地址信息
//参数3:struct sockaddr的大小
//返回值:成功返回0;失败返回-1,并更新errno

client:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define BUF_SIZE 20
//功能:TCPclient
int main()
{
//socket
//socket创建流式套接字的socket软通道
//参数1:AF_INET代表IPV4作为协议
//参数2:SOCK_STREAM代表创建流式套接字
//参数3:0自动匹配其他需要的协议
int clientfd = socket(AF_INET, SOCK_STREAM, 0);
//判断socket返回值
if(-1 == clientfd)
{
//返回值为-1,代表创建软通道出错,打印出错原因并返回
perror("socket error");
return -1;
}
//创建软通道成功
printf("socket ok----\r
");
//connect
//connect连接服务端
//参数1:socket创建的软通道的返回值
//参数2:所要连接服务端的端口号,IP地址,以及协议域结构体的指针
//参数3:参数2所指的结构体的大小
struct sockaddr_in serveraddr;
//使用struct sockaddr_in结构体接受参数2的参数并强转为struct sockaddr结构体
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(8888);
serveraddr.sin_addr.s_addr = inet_addr("127.0.0.1");
int ret = connect(clientfd, (struct sockaddr *)&serveraddr, sizeof(struct sockaddr_in));
//判断connect的返回值
if(-1 == ret)
{
//失败打印失败原因并返回
perror("connect error----\r
");
return -1;
}
//连接服务端成功
printf("connect ok....\r
");
//创建数据缓冲区
char buf[BUF_SIZE] = {0};
printf("please write:\r
");
//从标准输入端口输入数据,存储在buf里
fgets(buf, BUF_SIZE, stdin);
//send为发送数据
//参数1:软通道的文件描述符
//参数2:数据存放指针
//参数3:发送数据的大小
//参数4:阻塞发送
ret = send(clientfd, buf, BUF_SIZE, 0);
//返回值判断
if(-1 == ret)
{
//失败返回原因,不做返回,接受服务端的数据
perror("send error");
}
//清空接收数据的缓冲区
memset(buf, 0, BUF_SIZE );
//recv接收数据
//参数1:软通道的文件描述符
//参数2:接收数据的缓冲区
//参数3:接收数据的缓冲区的大小
//参数4:0代表阻塞接收
ret = recv(clientfd, buf, BUF_SIZE, 0);
//返回值判断
if(-1 == ret)
{
//失败返回原因并返回
perror("recv error");
return -1;
}
//成功打印接受的数据
printf("recv data : %s\r
", buf);
//关闭软通道描述符
close(clientfd);
return 0;	
}

网络编程(二)插图(7)

TCP通信中存在的数据传递现象(粘包)

网络编程(二)插图(8)

粘包是什么?

接收方一次将发送方多次发送的数据包一次接收了

如何避免粘包?

方法1:浪费空间,解决粘包
网络编程(二)插图(9)
总结:发送和接收两端的Buf一样大!就不会出现接收方过多接受数据(一次发送对应一次接收!)
方法2:浪费时间,解决粘包
网络编程(二)插图(10)
总结:CPU在处理两条数据时,需要等待,对于系统性能就会有打折。
方法3:制定协议,解决粘包
思想:多个业务可以一次性发送过去,对方只要能正确解析即可(解析标准:制定的协议)
优势:不存在浪费时间和空间,多次写入的数据就算被一次发送到对方,对方也是有解析的标准来处理
含有多条数据包的一个大包。

制定一个协议:多个数据包以&符号隔开
buf1 = “logon#hqyj#111111”;
buf2 = “register#www#123456”;
bufAll = buf1&buf2; —》如何实现?

方式1:拼接
使用strcat函数
strcat(bufAll, buf1);
strcat(bufAll, "&");
strcat(bufAll, buf2);
bufAll = bu1&buf2;
方式2:拼接
使用sprintf函数:向指定的地址空间写入指定格式的内容
sprintf(bufAll, "%s&%s", buf1, bu2);
服务器代码:
#include 
#include 
#include  /* See NOTES */
#include 
#include 
#include 
#include 
#include 
#include "./server.h"
//功能:搭建基于TCP的服务器
//字符串的解析
int analysis_string_func(char *buf, char **result)
{
if(NULL == buf || NULL == result){
printf("null error");
return NULL_ERROR;
}
//按照&解析
//将buf的首地址暂时赋值给*(result+0)
int index = 0;
*(result + index++) = buf;
while(*buf)
{
if('&' == *buf){
//将*buf赋值为'\0'
*buf = '\0';
buf++;
*(result + index++) = buf;
}
else{
buf++;
}
}
return OK;
}
//功能:服务器初始化
//参数:
// 参数1:服务器的IP地址
// 参数2:服务器的端口号
//返回值:成功监听套接字(被动的),失败返回失败原因
int server_initial(const char *IP, int PORT)
{
//入参检查
if(NULL == IP)
{
printf("ip null error!
");
return IP_ERROR;
}
if(PORT < 0)
{
printf("port is error!
");
return PORT_ERROR;
}
//1,创建套接字
int sockFd = socket(AF_INET, SOCK_STREAM, 0);
if(sockFd < 0){
perror("socket error");
return SOCKET_ERROR;
}
printf("server socket ok!
");
//2,绑定服务器的IP 和 端口号
//定义地址信息结构的变量并赋值,未为bind参数作准备
struct sockaddr_in serverAddr;//定义变量
bzero(&serverAddr, sizeof(serverAddr));//清空
//赋值
serverAddr.sin_family = AF_INET; //地址族
serverAddr.sin_port = htons(PORT);//端口号
serverAddr.sin_addr.s_addr = inet_addr(IP);//IP
if(bind(sockFd, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) <0)
{
perror("bind error!
");
return BIND_ERROR;
}
printf("bind server ok!
");
//3,创建监听队列
if(listen(sockFd, 5) < 0){
perror("listen error");
return LISTEN_ERROR;
}
printf("listening......
");
//返回经过listen之后的监听套接字
return sockFd;
}
//功能:与客户端通信
//参数:与客户端通信的套接字
//返回值:成功返回OK 失败返回失败原因
int server_com(int newFd)
{
if(newFd < 0)
{
printf("newFd is error");
return NEWFD_ERROR;
}
//先接收客户端发送的业务请求
char buf[512] = {0};
//业务接收
int recv_count = recv(newFd, buf, sizeof(buf), 0);
if(recv_count < 0){
perror("recv error");
return RECV_ERROR;
}
else if(0 == recv_count){
printf("客户端已退出!
");
return OK;
}
else{
printf("客户端业务请求:%s
",buf);
//解析buf 得到客户端的所有业务数据
//定义存储解析结果的空间
char *result[2] = {NULL};
if(analysis_string_func(buf, result) < 0)
{
printf("解析失败
");
return ANALYSIS_ERROR;
}
//打印解析的结果
printf("业务1: %s
",result[0]);
printf("业务2: %s
",result[1]);
//判断解析的结果是什么,进而响应即可
if(0 == strncasecmp(result[0], "logon", 5))
{
//登陆业务
printf("登陆业务......
");
}
if(0 == strncasecmp(result[1], "register", 8))
{
//注册业务
printf("注册业务......
");
}
}
return OK;
}
int main(int argc, const char *argv[])
{
//入参检查
if(argc < 3)
{
printf("参数 传参个数 异常!
");
return ERROR;
}
//子函数1:服务器的初始化(1,2,3)
int listenFd = server_initial(argv[1], atoi(argv[2]));//IP 端口号
if(listenFd < 0)
{
return ERROR;
}
//4,等待建立连接
int newFd = accept(listenFd, NULL, NULL);
if(newFd < 0){
perror("accept error");
return ACCEPT_ERROR;
}
printf("accept new client ok!
");
//5,通信业务
int ret = server_com(newFd);
if(ret < 0)
{
return ERROR;
}
printf("业务处理结束!
");
//6,关闭套接字
close(listenFd);
close(newFd);
return 0;
}
客户端代码:
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "./client.h"
int client_initial(const char *IP, int PORT)
{
int sockFd = socket(AF_INET, SOCK_STREAM, 0);
if(sockFd < 0){
perror("socket error");
return -1;
}
printf("client socket ok!
");
struct sockaddr_in serverAddr;
bzero(&serverAddr, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(PORT);
serverAddr.sin_addr.s_addr = inet_addr(IP);
if(connect(sockFd, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) <0){
perror("connect error");
return -2;
}
printf("connect to server ok!
");
return sockFd;
}
int client_com(int sockFd)
{
if(sockFd < 0){
printf("sockFd is error!
");
return -1;
}
//发送消息
char buf1[256] = {"logon#hqyj#111111"};
char buf2[256] = {"register#www#123456"};
char bufAll[512] = {0};
//使用sprintf将buf1和buf2中间加上&一起拼接到bufAll空间
sprintf(bufAll, "%s&%s", buf1, buf2);
//业务发送
int send_count = send(sockFd, bufAll, strlen(bufAll), 0);
if(send_count < 0){
perror("send error");
return -2;
}
else{
printf("send ok!
");
}
return 0;
}
int main(int argc, const char *argv[])
{
int sockFd = client_initial(argv[1], atoi(argv[2]));
if(sockFd < 0){
return -1;
}
int ret = client_com(sockFd);
if(ret < 0)
{
printf("处理失败!
");
return -1;
}
printf("业务办理结束!
");
close(sockFd);
return 0;
}

网络编程(二)插图(11)

本站无任何商业行为
个人在线分享 » 网络编程(二)
E-->