网络编程(二)

网络编程(二)

基于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;
}
相关推荐
2301_7756023829 分钟前
C++11 多线程编程-小白零基础到手撕线程池
开发语言·c++
码农豆豆44 分钟前
11.C++程序中的常用函数
开发语言·c++·算法
无极程序员1 小时前
读取到json数据拿出来,修改后重新写入json文件
android·java·开发语言·后端·json·php
艾伦~耶格尔1 小时前
Java 之 ssm框架入门
java·开发语言·后端·spring·mybatis·ssm
尘浮生1 小时前
Java项目实战II基于Java+Spring Boot+MySQL的美容院管理系统设计与实现(源码+数据库+文档)
java·开发语言·数据库·spring boot·mysql·maven·intellij-idea
jcfszxc1 小时前
【Rockchip系列】RGA imcopy 性能分析:不同缓冲区拷贝的对比(含实验代码)
开发语言·c++·rknn·rockchip·rknn-toolkit2
GSCSDNeo1 小时前
9.23 My_string.cpp
开发语言·c++
上晴下雪1 小时前
1. go 环境与命令
开发语言·后端·golang
曳渔1 小时前
Java-数据结构-Map和Set-(二)-哈希表 |ू・ω・` )
java·开发语言·数据结构·算法·哈希算法·散列表