Linux多人聊天室之前篇


前言

本文的内容是多人聊天室的前期工作,记录的是一个客户端和一个服务端聊天,尚未涉及到多线程。若想看多线程的内容,请看下篇。


一、聊天模式框架

先把一个服务端和客户端聊天框架先构建好

在这里插入图片描述

二、代码

服务端(server.c)代码如下(示例):

/************************************************************************
*
* 文件名:server.c
*
* 功能:服务端(一般用于接收数据)
*
* 创建人:LZH
*
* 时间:2021年11月13日22:53:00
*
* 版本号:1.0
*
* 修改记录:无
*
************************************************************************/
#include <sys/types.h>        
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <unistd.h>
#include <string.h>


int main(int argc, char const *argv[])
{
	//1、准备一个未连接的套接字
	int sockfd = socket(AF_INET, SOCK_STREAM, 0);

	//2、绑定一个IP地址到套接字上
	struct sockaddr_in seraddr;
	socklen_t len = sizeof(seraddr);
	bzero(&seraddr, len);            //赋值前先清空seraddr内存上数据
	seraddr.sin_family = AF_INET;	//设置服务器协议

	seraddr.sin_port = htons(atoi(argv[1]));		//设置服务器的端口号(把字符串转成短整型)
	seraddr.sin_addr.s_addr = htonl(INADDR_ANY);	//设置服务器的IP地址

	bind(sockfd, (struct sockaddr *)(&seraddr), len);

	//3、将未连接套接字转换成监听套接字
	listen(sockfd, 5);

	//4、不断等待监听套接字上的数据
	struct sockaddr_in cliaddr;
	bzero(&cliaddr, len);

	int connfd = accept(sockfd, (struct sockaddr *)&cliaddr, &len);  //谁连接到服务器上,谁的信息就会保存到这个变量中。
	if(connfd > 0) {
		printf("new connection:%d\n", connfd);
	} else {
		printf("accept function error!\n");
	}


	//5. 畅聊  不断接受客户端发送过来的数据
	char buf[100];
	while(1) {
		//5.1 清空缓冲区
		bzero(buf, sizeof(buf));

		//5.2 把已连接套接字上的数据读取到缓冲区
		recv(connfd,buf,sizeof(buf),0);
		
		//5.3 将缓冲区的数据打印出来
		printf("from client: %s",buf);
		
		//5.4 如果客户端给我发了quit,那么我就退出
		if( strncmp(buf,"quit",4) == 0 )
		{
			break;  //如果收到了quit,则跳出循环,程序结束
		}
	}
	
	//6. 挂断
	close(connfd);
	close(sockfd);
	
	return 0;
}

客户端(client.c)代码如下(示例):

/************************************************************************
*
* 文件名:client.c
*
* 功能:服务端(一般用于接收数据)
*
* 创建人:LZH
*
* 时间:2021年11月14日00:29:46
*
* 版本号:1.0
*
* 修改记录:无
*
************************************************************************/

#include <sys/types.h>        
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <unistd.h>
#include <string.h>

int main(int argc, char const *argv[])
{
	//1、准备一个未连接的套接字
	int sockfd = socket(AF_INET, SOCK_STREAM, 0);

	//2. 准备服务器的地址
	struct sockaddr_in seraddr;
	socklen_t len = sizeof(seraddr);
	bzero(&seraddr, len);            //赋值前先清空seraddr内存上数据
	seraddr.sin_family = AF_INET;	//设置服务器协议

	seraddr.sin_port = htons(atoi(argv[2]));		//设置服务器的端口号(把字符串转成短整型)
	inet_pton(AF_INET, argv[1], &seraddr.sin_addr); //设置服务器IP地址

	//3. 直接发起连接
	
	int ret = connect(sockfd, (struct sockaddr *)(&seraddr), len);
	if(ret == 0) {
		printf("connect success!\n");
	} else {
		printf("connect fail!\n");
	}

	//4. 不断发送数据给服务器。
	char buf[100];
	while(1)
	{
		//4.1 先清空缓冲区
		bzero(buf, sizeof(buf));
		
		//4.2 从键盘中获取字符串,然后把字符串存储到这个数组中。
		fgets(buf, sizeof(buf), stdin);  //stdin就是键盘设备对应的文件指针  
		
		//4.3 发送该字符串给服务器
		send(sockfd, buf, strlen(buf), 0);  //strlen(buf)可以计算出字符串实际的字符个数
		
		//4.4 判断发送的字符串是不是quit
		if( strncmp(buf, "quit", 4) == 0 )
		{
			break;  //如果收到了quit,则跳出循环,程序结束
		}
	}
	
	//5. 挂断电话
	close(sockfd);
	
	return 0;
}

三、结果显示:

在这里插入图片描述

四、网络编程通用API汇总

1.	创建未连接套接字:
	头文件:
		#include <sys/types.h>        
		#include <sys/socket.h>

	定义函数:
		int socket(int domain, int type, int protocol);

	参数:
		domain:域。
			AF_INET/PF_INET:网际协议
			AF_UNIX/PF_UNIX:本地协议,可写成AF_LOCAL/PF_LOCAL
		type:类型。
			SOCK_STREAM:流式套接字(TCP)
			SOCK_DGRAM:数据报套接字(UDP)
		protocol:协议。
			一般为0


		返回值:
			成功:待连接套接字
			失败:-1

备注:在网际协议中,选择流式套接字就代表TCP协议,选择数据包套接字就代表UDP协议,第三个参数protocol一般都不用。

======================================================

2、绑定套接字与网络地址
	定义函数:
		int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

	参数:
		sockfd:待连接套接字
		addr:包含本地地址(IP+PORT)的通用地址结构体的指针
		addrlen:地址结构体大小

	返回值:
		成功:0
		失败:-1

	备注:
		通用地址结构体的定义:
			struct sockaddr
			{
				sa_family_t  sa_family;   -> 协议 -> 2
				char        sa_data[14];  -> IP、端口号..  -> 14
			};

		特殊地址结构体 —— IPv4地址结构体:
			struct sockaddr_in
			{           
		       u_short sin_family;	// 协议  -> 2
		       u_short sin_port;	// 端口号  -> 2
		       struct in_addr sin_addr;	// IPV4地址  -> 4
		       char sin_zero[8];
			};

			struct in_addr
			{
				in_addr_t s_addr;	// 无符号32位网络地址 -> IP 
			};

			/* Address to accept any incoming messages. */
			#define	INADDR_ANY		((unsigned long int) 0x00000000)

	3.将待连接套接字设置为监听套接字,并设置最大同时接收连接请求个数
		定义函数:
			int listen(int sockfd, int backlog);

		参数:
			sockfd:待连接套接字
			backlog:最大同时接收连接请求个数

		返回值:
			成功:0,并将sockfd设置为监听套接字
			失败:-1

备注:
由于历史原因,各种系统对backlog的理解并不一致,以LINUX为例,监听端能同时接收的最大连接请求个数为backlog+4

======================================================

	4、等待对端连接请求
		定义函数:
			int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

		参数:
			sockfd:监听套接字
			addr:通用地址结构体,用以存储对端地址(IP+PORT)
			addrlen:参数addr的存储区域大小

		返回值:
			成功:已连接套接字(非负整数)
			失败:-1

======================================================


5、连接对端监听套接字
	接口声明:
		int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

	参数:
		sockfd:待连接套接字
		addr:包含对端地址(IP+PORT)的通用地址结构体的指针
		addrlen:地址结构体大小

	返回值:
		成功:0
		失败:-1

======================================================

6. 断开本端连接套接字
	头文件:
		#include <unistd.h>
	接口声明:int close(int fd);

	参数:
		fd:已连接套接字

	返回值:
		成功:0
		失败:-1

备注:
同时断开读端和写端

======================================================

7、将文本地址转化为二进制地址
	接口声明:
		int inet_pton(int af, const char *src, void *dst);

	参数:
		af:地址族。AF_INET:IPv4地址
		src:指向“点分式”IPv4或IPv6地址的指针,例如“192.168.1.100”
		dst:类型为struct in_addr *的指针

	返回值:
		成功:1
		失败:0代表地址与地址族不匹配,-1代表地址不合法

8、向TCP套接字发送数据
	接口声明:
		ssize_t send(int sockfd, const void *buf, size_t len, int flags);

	参数:
		sockfd:已连接套接字
		buf:即将被发送的数据
		len:数据长度
		flags:发送标志,置为0即可以。

	返回值:
		成功:已发送字节数
		失败:-1

9、从TCP套接字接收数据
	接口声明: 
		ssize_t recv(int sockfd, void *buf, size_t len, int flags);

	参数:
		sockfd:已连接套接字
		buf:存储数据缓冲区
		len:缓冲区大小
		flags:接收标志,置为0即可以。

	返回值:
		成功:已接收字节数
		失败:-1

10、字节序转换
	头文件:
		#include <arpa/inet.h> 

	接口声明:
		uint32_t htonl(uint32_t hostlong);   //h -> host 主机(本地)   n ->net 网络 
		uint16_t htons(uint16_t hostshort);  //l -> long 长4    s -> short 短2
		uint32_t ntohl(uint32_t netlong);
		uint16_t ntohs(uint16_t netshort);

	参数:
		hostlong:	主机字节序的长整型数据
		hostshort:	主机字节序的短整型数据
		netlong:	网络字节序的长整型数据
		netshort:	网络字节序的短整型数据

	返回值:
		对应的字节序数据


11、字符串转整型
	#include <stdlib.h>   
		atoi()
 


  • 0
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

free(me)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值