【网络编程】TCP的基础通信

网络通信之套接字

套接字通信原理

         socket函数介绍

int socket(int domain, int type, int protocol);

功能:为通信创建一个端点,并返回该端点的文件描述符

参数1:通信域

Name                                               Purpose                                                  Man page AF_UNIX, AF_LOCAL           本地通信,同一主机之间进程通信       详情请看man 7 unix AF_INET                                IPv4 提供的网络通信                           详情请看man 7 ip AF_INET6 IPv6                      提供的网络通信                                   详情请看man 7 ipv6

参数2:指定通信语义,可以由多个宏值使用位或连接

SOCK_STREAM:表示提供TCP协议的传输方式

SOCK_DGRAM:表示提供UDP协议的传输方式

SOCK_NONBLOCK:套接字设置非阻塞属性

参数3:如果参数2中仅仅指定一个协议,那么参数3可以填0,如果指定多个,则参数3需要指定特定的协议

        TCP协议名称:IPPROTO_TCP

        UDP协议名称:IPPROTO_UDP

返回值:成功返回创建的套接字文件描述符,失败返回 -1并置位错误码

 TCP实现网络通信

TCP网络通信原理图

 TCP相关函数介绍

        1> bind绑定

        int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

功能:位套接字分配名称

参数1:通过socket函数创建出来的套接字文件描述符

参数2:通用地址信息结构体,需要根据具体使用的地址族而定, struct sockaddr仅仅只是为了类型的强制转换,防止出现警告

跨主机间通信:man 7 ip

        struct sockaddr_in {

                sa_family_t sin_family; /* 表示通信域 */

                in_port_t sin_port; /* 端口号的网络字节序 */

                struct in_addr sin_addr; /* ip地址 */

        };

        /* Internet address. */

        struct in_addr {

                uint32_t s_addr; /* IP地址的网络字节序 */

        };

同一主机间通信:man 7 uninx

        struct sockaddr_un {

                sa_family_t sun_family; /* 表示通信域:AF_UNIX */

                char       sun_path[108]; /* 套接字文件的地址 */

        };

参数3:参数2的大小

返回值:成功返回0,失败返回-1并置位错误码

注意关于bind的两个错误:

1、 Cannot assign requested address:表示IP地址填写错误,检查IP是否有问题

2、Address already in use:表示地址信息正在占用,可以调用函数快速重用,也可以等一会

        2> listen监听

int listen(int sockfd, int backlog);

功能:将套接字设置成被动监听状态,已接受客户端的连接请求

参数1:套接字文件描述符

参数2:容纳连接的队列的最大长度,一般填128

返回值:成功返回0,失败返回-1并置为错误码

        3> accept接收连接请求

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

功能:用于阻塞接收客户端连接请求

参数1:服务器套接字文件描述符

参数2:用于接收对端地址信息结构体的指针

参数3:接收对端地址信息的长度

返回值:成功返回一个新的用于通信的套接字文件描述符,失败返回-1并置位错误码

        4> recv接收数据

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

功能:从套接字中读取数据到buf中

参数1:用于通信的套接字文件描述符

参数2:接收数据后的容器地址

参数3:接收的数据的大小

参数4:是否阻塞接收 0:表示阻塞接收消息 MSG_DONTWAIT:表示非阻塞接收数据

返回值: >0:表示成功读取的字符个数

                =0:表示通信对端已经下线

                =-1:表示出错,置位错误码

       5> send发送数据

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

功能:向通信套接字文件描述符中写入数据

参数1:通信的套接字文件描述符

参数2:要发送数据的起始地址

参数3:要发送数据的大小

参数4:是否阻塞接收 0:表示阻塞接收消息 MSG_DONTWAIT:表示非阻塞接收数据

返回值:成功返回发送字符的个数,失败返回-1并置位错误码

 6> connect连接函数

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

功能:将套接字文件描述符连接到addr指向的地址空间中

参数1:客户端套接字文件描述符

参数2:对端地址信息结构体

参数3:参数2的大小

返回值:成功返回0,失败返回-1并置位错误码

 TCP服务器端代码实现

#include<myhead.h>
#define SER_PORT 6666 //服务器端口号
#define SER_IP "192.168.119.143"
int main(int argc, const char *argv[])
{
	//创建套接字
	int sfd = socket(AF_INET,SOCK_STREAM,0);
	//参数1:标示ipv4的网络通信
	//参数2:表示使用TCP通信方式
	//参数3:表示默认使用一个协议
	if (sfd == -1)
	{
		perror("socket errror");
		return -1;
	}
	printf("socket success ,sfd = %d\n",sfd);
		//将端口号快速重用
	int reuse = 1;
	if(setsockopt(sfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse))==-1)
	{
		perror("setsockopt error");
		return -1;
	}
	printf("端口号快速重用成功\n");

	//2.为套接字绑定ip地址和端口号
	//2.1填充地址信息结构体
	struct sockaddr_in sin;
	sin.sin_family = AF_INET; //通信域
	sin.sin_port = htons (SER_PORT);//端口号
	sin.sin_addr.s_addr = inet_addr(SER_IP);//ip地址
	//2.2绑定工作
	if(bind(sfd,(struct sockaddr *)&sin,sizeof(sin))==-1)
	{
		perror("bind error");
		return -1;
	}
	printf("bind success\n");
	//3将套接字设置成被动监听
	if(listen(sfd,128)==-1)
	{
		perror("listen error");
		return -1;
	}
	printf("listen success\n");
	//4阻塞等待客户端的连接请求
	//4.1定义变量用于接收客户端的信息
	struct sockaddr_in cin ;//用于接收地址信息
	socklen_t addrlen = sizeof(cin);//用于接收长度
	int newfd = accept(sfd,(struct sockaddr*)&cin,&addrlen);
	if(newfd==-1)
	{
		perror("accept error");
		return -1;
	}
	printf("[%s:%d]已经成功连接一个客户端\n",inet_ntoa(cin.sin_addr),ntohs(cin.sin_port));
	//5.数据收发
	char buf[128] = "";
	while(1)
	{
		//清空缓冲区
		bzero(buf,sizeof(buf));
		//从客户端套接字中读取数据
		int res = recv(newfd,buf,sizeof(buf),0);
		if(res == -1)
		{
			perror("read error");
			return -1;
		}else if(res == 0)
		{
			printf("客户端以下线\n");
			close(newfd);//关闭客户端套接字
			break;
		}
		//正常接收客户端发来的消息
		printf("[%s:%d]:%s\n",inet_ntoa(cin.sin_addr),ntohs(cin.sin_port),buf);
		strcat(buf,"*-*");
		//将消息回复给客户端
		if(send(newfd,buf,strlen(buf),0)==-1)
		{
			perror("write error");
			return -1;
		}
		printf("发送成功\n");
	}
	close(newfd);
	return 0;
}

TCP客户端实现

#include<myhead.h>
#define SER_PORT 6666 //与服务器一致
#define SER_IP "192.168.119.143" //服务器ip地址
#define CLI_PORT 8888//客户端端口号
#define CLI_IP "192.168.119.143"//客户端ip地址
int main(int argc, const char *argv[])
{
	//1.创建用于通信的套接字文件描述符
	int cfd = socket(AF_INET,SOCK_STREAM,0);
	if(cfd == -1)
	{
		perror("socket error");
		return -1;

	}
	printf("cfd = %d\n",cfd);
	//2.绑定IP地址和端口号
	//2.1填充地址信息结构体
	struct sockaddr_in cin;
	cin.sin_family = AF_INET; //通信域
	cin.sin_port = htons (CLI_PORT);//端口号
	cin.sin_addr.s_addr = inet_addr(CLI_IP);//ip地址
	//2.2绑定工作
	if(bind(cfd,(struct sockaddr *)&cin,sizeof(cin))==-1)
	{
		perror("bind error");
		return -1;
	}
	printf("bind success\n");

	//3.连接到服务器
	//3.1填充服务器地址信息结构体
	struct sockaddr_in sin;
	sin.sin_family = AF_INET;//通信域
	sin.sin_port = htons(SER_PORT);//服务器端口号
	sin.sin_addr.s_addr = inet_addr(SER_IP);//服务器ip地址
	//3.2连接到fwq
	if(connect(cfd,(struct sockaddr*)&sin,sizeof(sin))==-1)
	{
		perror("connect error");
		return -1;
	}
	printf("连接服务器成功\n");
	//4.数据收发
	char buf[128] = "";
	while(1)
	{
		printf("请输入>>>");
		fgets(buf,sizeof(buf),stdin);//从终端获取字符串
		buf[strlen(buf)-1]=0;
		//将数据发送给服务器
		send(cfd,buf,strlen(buf),0);
		printf("发送成功\n");
		if(strcmp(buf,"quit")==0)
		//接收服务器传来的数据
		//清空容器
		bzero(buf,sizeof(buf));
		recv(cfd,buf,sizeof(buf),0);
		printf("收到服务器消息为:%s\n",buf);
	}
	//5.关闭套接字
	close(cfd);
	return 0;
}

TCP服务器通信模型

1、sfd = socket(); //创建一个用于连接的套接字文件描述符

2、bind(); //为服务器套接字绑定ip地址和端口号,为了让客户端额能够找到服务器

3、listen(); //将服务器套接字设置成被动监听状态,用于接收客户端的连接请求

4、newfd = accept(); //阻塞等待客户端的连接请求,如果有客户端发来连接请求,创建一个                                      新的用于通信的套接字文件描述符

5、while(1) {

                        send\recv\read\write; //数据收发工作

                    }

6、close(); //关闭套接字、关闭监听

 UDP实现网络通信

UDP网络通信模型

 UDP相关函数

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);

//功能:从套接字文件描述符中读取数据,并将对端地址信息结构体接收

参数1:套接字文件描述符

参数2:要接收数据的起始地址

参数3:要接收的数据大小

参数4:是否阻塞,0表示阻塞,MSG_NOWAIT表示非阻塞

参数5:接收对端地址信息结构体

参数6:参数5的大小

返回值:成功返回读取的字节的大小,失败返回-1并置位错误码

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);

//功能:向套接字文件描述符中读取数据,写给指定的对端接收

参数1:套接字文件描述符

参数2:要发送数据的起始地址

参数3:要发送的数据大小

参数4:是否阻塞,0表示阻塞,MSG_NOWAIT表示非阻塞

参数5:接收对端地址信息结构体

参数6:参数5的大小 返回值:成功返回发送的字节的大小,失败返回-1并置位错误码

 UDP服务器端实现

#include<myhead.h>
#define SER_PORT 9999
#define SER_IP "192.168.119.143"
int main(int argc, const char *argv[])
{
	//1,创建用于通讯的服务器套接字文件描述符
	int sfd = socket(AF_INET,SOCK_DGRAM,0);
	if(sfd == -1)
	{
		perror("scoket error");
		return -1;
	}
	printf("sfd = %d\n",sfd);
	//2.绑定IP地址和端口号
	//2.1填充地址信息结构体
	struct sockaddr_in sin;
	sin.sin_family = AF_INET;//通讯域
	sin.sin_port = htons(SER_PORT);//端口号
	sin.sin_addr.s_addr = inet_addr(SER_IP);//ip地址
	//2.2绑定工作
	if(bind(sfd , (struct sockaddr*)&sin,sizeof(sin))==-1)
	{
		perror("bind error");
		return -1;
	}
	printf("bind success\n");
	//3.数据收发
	char buf[128]="";
	struct sockaddr_in cin;//接收对端地址信息
	socklen_t addrlen = sizeof(cin);//接收地址长度
	while(1)
	{
		//清空容器
		bzero(buf,sizeof(buf));
		//从套接字中读取数据
		recvfrom(sfd,buf,sizeof(buf),0,(struct sockaddr*)&cin,&addrlen);
		if(strcmp(buf,"quit")==0)
		{
			break;
		}
		printf("收到消息为:%s\n",buf);
		
		strcat(buf,"*-*");
		if(sendto(sfd,buf,sizeof(buf),0,(struct sockaddr*)&cin,sizeof(cin))== -1)
		{
			perror("write error");
			return -1;
		}
		printf("发送成功\n");
	}
	//4.关闭文件描述符
	close(sfd);
	return 0;
}

UDP客户端实现

#include<myhead.h>
#define SER_PORT 9999
#define SER_IP "192.168.119.143"
#define CLI_PORT 5555
#define CLI_IP "192.168.119.143"
int main(int argc, const char *argv[])
{
	//1,创建用于通讯的服务器套接字文件描述符
	int cfd = socket(AF_INET,SOCK_DGRAM,0);
	if(cfd == -1)
	{
		perror("scoket error");
		return -1;
	}
	printf("cfd = %d\n",cfd);
	//2.绑定IP地址和端口号
	//2.1填充地址信息结构体
	struct sockaddr_in cin;
	cin.sin_family = AF_INET;//通讯域
	cin.sin_port = htons(CLI_PORT);//端口号
	cin.sin_addr.s_addr = inet_addr(CLI_IP);//ip地址
	//2.2绑定工作
	if(bind(cfd , (struct sockaddr*)&cin,sizeof(cin))==-1)
	{
		perror("bind error");
		return -1;
	}
	printf("bind success\n");
	//3.数据收发
	char buf[128]="";
	struct sockaddr_in sin;//接收对端地址信息
	sin.sin_family = AF_INET;
	sin.sin_port = htons(SER_PORT);
	sin.sin_addr.s_addr = inet_addr(SER_IP);

	while(1)
	{	
		//从终端获取数据
		printf("请输入>>>");
		fgets(buf,sizeof(buf),stdin);
		buf[strlen(buf)-1]=0;
		//将数据发生给服务器
		sendto(cfd,buf,strlen(buf),0,(struct sockaddr*)&sin,sizeof(sin));
		if(strcmp(buf,"quit")==0)
		{
			break;
		}
		//清空容器
		bzero(buf,sizeof(buf));
		//接收服务器发来的消息
		recvfrom(cfd,buf,sizeof(buf),0,NULL,NULL);
		printf("接收到服务器消息为:%s\n",buf);
	}
	//4.关闭文件描述符
	close(cfd);
	return 0;
}

 

  • 13
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值