网络编程(二)

网络编程(二)

基于TCP协议的网络客户端

服务端

步骤:

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

socket:创建网络软通道

#include <sys/types.h>

#include <sys/socket.h>
int socket(int domain, int type, int protocol);

//参数1:协议域(AF_INET)ipv4

//参数2:套接字类型-->SOCK_STREAM 流式套接字

//参数3:其他协议-->0:自动匹配其他需要的协议

//返回值:成功返回文件描述符,标识socket网络软通道;失败返回-1,更新errno

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

#include <sys/types.h>

#include <sys/socket.h>
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



inet_addr():将点分十进制IP地址转为32位无符号整数

c 复制代码
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
in_addr_t inet_addr(const char *cp);

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

#include <sys/types.h>

#include <sys/socket.h>
int listen(int sockfd, int backlog);

//参数1:文件描述符-->socket返回值

//参数2:最大监听客户端的个数

//返回值:成功返回0;失败返回-1,并更新errno

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

#include <sys/types.h>

#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

//参数1:文件描述符-->socket返回值

//参数2:指向存放对方的主机信息结构体

//参数3:指向struct sockaddr的大小的指针

//返回值:成功返回新的文件描述符,标识一个新的网络软通道,用作收发正文数据(recv/send);失败返回-1,并更新errno

recv:接收数据

#include <sys/types.h>

#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);

//参数1:文件描述符-->accept的返回值

//参数2:用于存放接收数据的BUF

//参数3:期望接收数据的大小

//参数4:阻塞&非阻塞的标志 0-->阻塞

//返回值:成功返回接收的字节数;失败返回-1,并更新errno

send:发送数据

#include <sys/types.h>

#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);

//参数1:文件描述符-->accept的返回值

//参数2:用于存放接收数据的BUF

//参数3:期望发送数据的大小

//参数4:阻塞&非阻塞的标志 0-->阻塞

//返回值:大于0,发送的字节个数;发送失败返回-1,并更新errno

server:

c 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#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\n");
	//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\n");
	//listen
	ret = listen(serverfd, 5);
	//返回值判断
	if(-1 == ret)
	{
		//失败打印失败原因并返回
		perror("listen error");
		return -1;
	}
	//监听创建成功
	printf("listen ok------\r\n");
	//接收客户端的信息
	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\n");
		//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\n", buf);
		memset(buf, 0, BUF_SIZE);
		printf("please data:\r\n");
		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)

connect:连服务端

#include <sys/types.h>

#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

//参数1:文件描述符-->socket返回值

//参数2:用于存放服务端的地址信息

//参数3:struct sockaddr的大小

//返回值:成功返回0;失败返回-1,并更新errno

client:

c 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#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\n");
	//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\n");
		return -1;
	}
	//连接服务端成功
	printf("connect ok....\r\n");
	//创建数据缓冲区
	char buf[BUF_SIZE] = {0};
	printf("please write:\r\n");
	//从标准输入端口输入数据,存储在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\n", buf);
	//关闭软通道描述符
	close(clientfd);
	return 0;	
}

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

粘包是什么?

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

如何避免粘包?

方法1:浪费空间,解决粘包

总结:发送和接收两端的Buf一样大!就不会出现接收方过多接受数据(一次发送对应一次接收!)
方法2:浪费时间,解决粘包

总结:CPU在处理两条数据时,需要等待,对于系统性能就会有打折。
方法3:制定协议,解决粘包

思想:多个业务可以一次性发送过去,对方只要能正确解析即可(解析标准:制定的协议)

优势:不存在浪费时间和空间,多次写入的数据就算被一次发送到对方,对方也是有解析的标准来处理

含有多条数据包的一个大包。

制定一个协议:多个数据包以&符号隔开

buf1 = "logon#hqyj#111111";

buf2 = "register#www#123456";

bufAll = buf1&buf2; ---》如何实现?

c 复制代码
方式1:拼接
使用strcat函数
strcat(bufAll, buf1);
strcat(bufAll, "&");
strcat(bufAll, buf2);
bufAll = bu1&buf2;
方式2:拼接
使用sprintf函数:向指定的地址空间写入指定格式的内容
sprintf(bufAll, "%s&%s", buf1, bu2);
c 复制代码
服务器代码:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <strings.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#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!\n");
		return IP_ERROR;
	}
	if(PORT < 0)
	{
		printf("port is error!\n");
		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!\n");
	//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!\n");
		return BIND_ERROR;
	}
	printf("bind server ok!\n");
	//3,创建监听队列
	if(listen(sockFd, 5) < 0){
		perror("listen error");
		return LISTEN_ERROR;
	}
	printf("listening......\n");
	//返回经过listen之后的监听套接字
	return sockFd;
}
c 复制代码
//功能:与客户端通信
//参数:与客户端通信的套接字
//返回值:成功返回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("客户端已退出!\n");
		return OK;
	}
	else{
		printf("客户端业务请求:%s\n",buf);
		//解析buf 得到客户端的所有业务数据
		//定义存储解析结果的空间
		char *result[2] = {NULL};
		if(analysis_string_func(buf, result) < 0)
		{
			printf("解析失败\n");
			return ANALYSIS_ERROR;
		}
		//打印解析的结果
		printf("业务1: %s\n",result[0]);
		printf("业务2: %s\n",result[1]);
		//判断解析的结果是什么,进而响应即可
		if(0 == strncasecmp(result[0], "logon", 5))
		{
			//登陆业务
			printf("登陆业务......\n");
		}
		if(0 == strncasecmp(result[1], "register", 8))
		{
			//注册业务
			printf("注册业务......\n");
		}
	}
	return OK;
}
int main(int argc, const char *argv[])
{
	//入参检查
	if(argc < 3)
	{
		printf("参数 传参个数 异常!\n");
		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!\n");
	//5,通信业务
	int ret = server_com(newFd);
	if(ret < 0)
	{
		return ERROR;
	}
	printf("业务处理结束!\n");
	//6,关闭套接字
	close(listenFd);
	close(newFd);
	return 0;
}
c 复制代码
客户端代码:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <strings.h>
#include <stdlib.h>
#include <string.h>
#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!\n");
	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!\n");
	return sockFd;
}
int client_com(int sockFd)
{
	if(sockFd < 0){
		printf("sockFd is error!\n");
		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!\n");
	}
	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("处理失败!\n");
		return -1;
	}
	printf("业务办理结束!\n");
	close(sockFd);
	return 0;
}
相关推荐
JaguarJack8 小时前
FrankenPHP 原生支持 Windows 了
后端·php·服务端
BingoGo8 小时前
FrankenPHP 原生支持 Windows 了
后端·php
JaguarJack1 天前
PHP 的异步编程 该怎么选择
后端·php·服务端
BingoGo1 天前
PHP 的异步编程 该怎么选择
后端·php
JaguarJack2 天前
为什么 PHP 闭包要加 static?
后端·php·服务端
ServBay3 天前
垃圾堆里编码?真的不要怪 PHP 不行
后端·php
用户962377954483 天前
CTF 伪协议
php
BingoGo5 天前
当你的 PHP 应用的 API 没有限流时会发生什么?
后端·php
JaguarJack5 天前
当你的 PHP 应用的 API 没有限流时会发生什么?
后端·php·服务端
BingoGo6 天前
OpenSwoole 26.2.0 发布:支持 PHP 8.5、io_uring 后端及协程调试改进
后端·php