Linux系统下建立Socket聊天服务器

目录

1.服务器结构

2.各模块函数

[2.1 socket函数](#2.1 socket函数)

[2.2 bind函数](#2.2 bind函数)

[2.3 Listen函数](#2.3 Listen函数)

[2.4 accept函数](#2.4 accept函数)

[2.5 接收发送函数](#2.5 接收发送函数)

[2.6 close函数](#2.6 close函数)

[2.7 connect函数](#2.7 connect函数)

[3 代码段](#3 代码段)

[3.1 服务器代码](#3.1 服务器代码)


1.服务器结构

使用socket的API函数编写服务端和客户端程序的步骤图示:

2.各模块函数

服务器:

2.1 socket函数

使用socket会建立一个服务器文件描述符

  • 成功: 返回一个大于0的文件描述符

  • 失败: 返回-1, 并设置errno

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

domain: 协议版本

复制代码
AF_INET IPV4
AF_INET6 IPV6
AF_UNIX AF_LOCAL本地套接字使用

type:协议类型

复制代码
SOCK_STREAM 流式, 默认使用的协议是TCP协议
SOCK_DGRAM  报式, 默认使用的是UDP协议

protocal:

复制代码
一般填0, 表示使用对应类型的默认协议.

2.2 bind函数

成功: 返回0

失败: 返回-1, 并设置errno

复制代码
 int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

函数描述: 将socket文件描述符和IP,PORT绑定。

sockfd为socket的返回值,文件描述符

struct sockaddr* addr结构体可以用下面的

cpp 复制代码
struct sockaddr_in serv;
serv.sin_family = AF_INET;//选择使用的网络协议
serv.sin_port = htons(8888);//绑定本机端口,通常占2字节。注意:端口号尽量不要填1024以前的数字,因为可以被系统预留了。
serv.sin_addr.s_addr = htonl(INADDR_ANY);//INADDR_ANY: 表示使用本机任意有效的可用IP
如果想自己指定ip地址作为服务器连接就需要这个:
inet_pton(AF_INET, "127.0.0.1", &serv.sin_addr.s_addr);
或者inet_aton("127.0.0.1", &serv.sin_addr);
或者这个:addr.sin_addr.s_addr = inet_addr("192.168.239.1");

同时在使用addr时,先对其进行清空memset。

"端口号所谓的端口,就好像是门牌号一样,客户端可以通过ip地址找到对应的服务器端,但是服务器端是有很多端口的,每个应用程序对应一个端口号,通过类似门牌号的端口号,客户端才能真正的访问到该服务器。为了对端口进行区分,将每个端口进行了编号,这就是端口号。"

你可能对出现的htons()、htonl和inet_pton()不知道是何意,在网络传输中,不同的机器端不一样,有的机器是大端有的机器是小端。这些函数是为了帮助你在传输网络数据的时候统一格式。(没有超过一个字节不需要转)

cpp 复制代码
大端: 低位地址存放高位数据, 高位地址存放低位数据(也叫网络字节序)
小端: 低位地址存放低位数据, 高位地址存放高位数据(也叫小端字节序)

网络中传输使用的是大端法,如果机器使用的是小端,则需要进行大小端的转换。

下面4个函数就是进行大小端转换的函数:

cpp 复制代码
  #include <arpa/inet.h>
       uint32_t htonl(uint32_t hostlong);
       uint16_t htons(uint16_t hostshort);
       uint32_t ntohl(uint32_t netlong);
       uint16_t ntohs(uint16_t netshort);

函数名的h表示主机host, n表示网络network, s表示short, l表示long

上述的几个函数, 如果本来不需要转换函数内部就不会做转换.

IP地址转换函数:

p->表示点分十进制的字符串形式

to->到

n->表示network网络

cpp 复制代码
int inet_pton(int af, const char *src, void *dst);

函数说明: 将字符串形式的点分十进制IP转换为大端模式的网络IP(整形4字节数)

参数说明:

复制代码
af: AF_INET
src: 字符串形式的点分十进制的IP地址
dst: 存放转换后的变量的地址

如192.168.232.145, 先将4个正数分别转换为16进制数,

192--->0xC0 168--->0xA8 232--->0xE8 145--->0x91

最后按照大端字节序存放: 0x91E8A8C0, 这个就是4字节的整形值.

cpp 复制代码
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);

函数说明: 网络IP转换为字符串形式的点分十进制的IP

参数说明:

cpp 复制代码
af: AF_INET
src: 网络的十六进制的IP地址
dst: 转换后的IP地址,一般为字符串数组
size: dst的长度
cpp 复制代码
成功--返回执行dst的指针
失败--返回NULL, 并设置errno

如 IP地址为010aa8c0, 转换为点分十进制的格式:

01---->1 0a---->10 a8---->168 c0---->192

由于从网络中的IP地址是高端模式, 所以转换为点分十进制后应该为: 192.168.10.1

2.3 Listen函数

cpp 复制代码
int listen(int sockfd, int backlog);

成功: 返回0 失败: 返回-1, 并设置errno

函数描述: 将套接字由主动态变为被动态,也就是设置为监听文件描述符。

参数说明:

cpp 复制代码
sockfd: 调用socket函数返回的文件描述符
backlog: 同时请求连接的最大个数(还未建立连接)  设置为6/7
 注意:在linux系统中,这里代表全连接队列(已连接队列)的数量。在unix系统种,这里代表全连接队列(已连接队列)+ 半连

2.4 accept函数

cpp 复制代码
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);	

函数说明:获得一个连接, 若当前没有连接则会阻塞等待.

cpp 复制代码
sockfd: 调用socket函数返回的文件描述符
addr: 传出参数, 保存客户端的地址信息。如果不关心可以传NULL。
addrlen: 传入传出参数,  addr变量所占内存空间大小,这个传出的时候会告诉我们填充的多少的内容。如果不关心可以传NULL。

成功: 返回一个新的文件描述符,用于和客户端通信 失败: 返回-1, 并设置errno值.

accept函数是一个阻塞函数, 若没有新的连接请求, 则一直阻塞。从已连接队列中获取一个新的连接, 并获得一个新的文件描述符, 该文件描述符用于和客户端通信. (内核会负责将请求队列中的连接拿到已连接队列中)。

2.5 接收发送函数

接下来就可以使用write和read函数进行读写操作了。除了使用read/write函数以外, 还可以使用recv和send函数。

cpp 复制代码
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t send(int sockfd, const void *buf, size_t len, int flags);    
//对应recv和send这两个函数flags直接填0就可以了.

fd为accept返回的fd,count为字节,flag写0

注意: 如果写缓冲区已满, write也会阻塞, read读操作的时候, 若读缓冲区没有数据会引起阻塞.

2.6 close函数

最后通讯完之后记得close()文件描述符,关闭文件描述符后就断开了连接,就从已连接队列里面去掉了.。

2.7 connect函数

cpp 复制代码
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

函数说明: 连接服务器,client.c使用connect函数前应该先使用socket函数得到文件描述符fd。

函数参数:

addr设置为服务端一样的就行,进行传入

cpp 复制代码
sockfd: 调用socket函数返回的文件描述符
addr: 服务端的地址信息
addrlen: addr变量的内存大小   用sizeof

返回值:

cpp 复制代码
成功: 返回0
失败: 返回-1, 并设置errno值

主要用于客户端连接,客户端不需要绑定端口、ip什么的,因为只要能连上然后传输接收数据就行。

然后直接用sockfd进行读写就行。

3 代码段

3.1 服务器代码

cpp 复制代码
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <ctype.h>
#include <arpa/inet.h>

int main(void)
{
	int s_fd,ss_fd,nread,len;
	char buf[32];
	char msg[32];
	struct sockaddr_in s_ddr;  //build server msg
	struct sockaddr_in c_ddr;  //save clinet msg
	s_fd= socket(AF_INET, SOCK_STREAM, 0);//1.build a soket specified
	if(s_fd==-1){
		perror("error is");
	}
	//2.build all bind
	s_ddr.sin_family=AF_INET;
	s_ddr.sin_port=htons(8880);
	s_ddr.sin_addr.s_addr=htonl(INADDR_ANY);
	//give the bind
	bind(s_fd,(struct sockaddr *)&s_ddr,sizeof(s_ddr));
	//3.waite for client
	listen(s_fd,8);
	//4.accept come and connect for once
	len=sizeof(c_ddr);
	while(1){                        //这里用while1是为了一直可以被连接
		ss_fd=accept(s_fd,(struct sockaddr *)&c_ddr,&len);  
		printf("conect succese!==========\r\n");
		//5.read from connect ss_fd
		if(fork()==0){               //创建一个子进程(服务员)去接待client
			if(fork()==0){  //fork is zero is child pid   //创建一个子进程去等待发送
				//5.1  send
				while(1){
					memset(msg,0,32);	
					printf("input:");
					gets(msg);
					send(ss_fd,msg,32,0);
				}

			}
			//5.2 read	                                    //在父进程中等待接收数据
			while(1){
				memset(buf,'\0',32);
				nread=read(ss_fd,&buf,32);
				printf("server  receved :%s \r\n",buf); 
			}
		}

	}
	close(ss_fd);
	close(s_fd);
	return 0;
}

3.2 客户端代码

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>


int main(int argc,char *argv[])
{
	int flag,s_fd,n_read;
	struct sockaddr_in c_ddr;
	char readbuf[32];
	char msg[32];
	//1.build socket
	s_fd=socket(AF_INET,SOCK_STREAM,0);
	
	//2.0 prepare server addr
	memset(&c_ddr,0,sizeof(c_ddr)); //clear c_ddr
	c_ddr.sin_family=AF_INET;
	c_ddr.sin_port=htons(8880);
	inet_aton("192.168.102.141",&c_ddr.sin_addr);
	
	//2.connect server get s_fd
			
	if(connect(s_fd,(struct sockaddr *)&c_ddr,sizeof(c_ddr))==-1){
		perror("error");
	}
	printf("connect success==============\r\n");
	while(1){                                      //while1父进程一直等待读数据        
		//recv  will block  
		memset(readbuf,0,32);
		read(s_fd,readbuf,32);
		printf("form server:%s\r\n",readbuf);
		
		//send
		if(fork()==0){  //fork is zero is child pid   //子进程一直(while1)等待发数据
			while(1){
				memset(msg,0,32);
				printf("input :::::");
				gets(msg);
				send(s_fd,msg,32,0);
			}
		}
	}
	close(s_fd);

	return 0;
} 

参考博文:

Linux环境下socket服务器搭建_socket搭建linux_master cat的博客-CSDN博客

socket编程 服务器_socket 服务器_不爱学习的王小二的博客-CSDN博客

相关推荐
七夜zippoe4 小时前
CANN Runtime任务描述序列化与持久化源码深度解码
大数据·运维·服务器·cann
盟接之桥4 小时前
盟接之桥说制造:引流品 × 利润品,全球电商平台高效产品组合策略(供讨论)
大数据·linux·服务器·网络·人工智能·制造
Fcy6485 小时前
Linux下 进程(一)(冯诺依曼体系、操作系统、进程基本概念与基本操作)
linux·运维·服务器·进程
袁袁袁袁满5 小时前
Linux怎么查看最新下载的文件
linux·运维·服务器
代码游侠5 小时前
学习笔记——设备树基础
linux·运维·开发语言·单片机·算法
主机哥哥6 小时前
阿里云OpenClaw部署全攻略,五种方案助你快速部署!
服务器·阿里云·负载均衡
Harvey9036 小时前
通过 Helm 部署 Nginx 应用的完整标准化步骤
linux·运维·nginx·k8s
珠海西格电力科技7 小时前
微电网能量平衡理论的实现条件在不同场景下有哪些差异?
运维·服务器·网络·人工智能·云计算·智慧城市
释怀不想释怀7 小时前
Linux环境变量
linux·运维·服务器
zzzsde7 小时前
【Linux】进程(4):进程优先级&&调度队列
linux·运维·服务器