我的第一篇博文——简单的C/S模型

本文记录了一次从零开始的C/S模型socket编程实践,详细解析了服务器与客户端的代码实现,总结了socket编程中关键的头文件、结构体及函数。并通过解决实际编程中遇到的问题,如连接模式匹配和字符串比较,分享了编程经验和教训。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这几天在学习Linux环境下的基础socket编程,作为一个小实验,自己编写了一个最基本简单的C/S模型,然而并没有像我想当然的那样一次性成功。一些错误来源于概念的偏差,而一些来源于对细节的忽略。总的来说,这次小小的经历对本人来说受益颇多,故此将其写成博文,做个纪念,也方便今后查阅总结。

首先,就我的理解来说一下C/S模型,不足之处还请各位多多批评。

C/S模型,或者说架构,即是Server/Client机构。其组成分为服务器端与客户端。服务器首先开启,创建socket接口,绑定并保持监听,其采取的是被动式连接,即不主动连接而等待客户端的连接请求。客户端根据服务器的网络地址和其提供的接口主动进行连接请求。服务器接受请求后通信正式开始。

下面我将介绍我所写的C/S模型的具体内容,先上代码(没有注释,抱歉 ^ w ^ )

服务器端:

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

#define SERVER_PORT 5555
#define BUFFSIZE 2048
#define ADDR_LEN sizeof(Sockaddr_in)

typedef struct sockaddr_in Sockaddr_in;
typedef struct sockaddr Sockaddr;

int main(void){
	int server_socket;
	int client_socket;
	Sockaddr_in server_addr;
	Sockaddr_in client_addr;
	char buffer[BUFFSIZE];

	if((server_socket=socket(AF_INET,SOCK_STREAM,0))<0){
		perror("socket");
		exit(1);
	}
	fputs("socket created!\n",stdout);

	bzero(&server_addr,ADDR_LEN);
	server_addr.sin_family=AF_INET;
	server_addr.sin_port=htons(SERVER_PORT);
	server_addr.sin_addr.s_addr=htonl(INADDR_ANY);

	if(bind(server_socket,(Sockaddr *)&server_addr,ADDR_LEN)<0){
		perror("connect");
		exit(1);
	}
	fputs("socket bond!\n",stdout);

	if(listen(server_socket,5)<0){
		perror("listen");
		exit(1);
	}
	fputs("listening...done!\n",stdout);

	fputs("acception waiting...\n",stdout);
	client_socket=-1;
	int addr_len=ADDR_LEN;
	while(client_socket<0)
		client_socket=accept(server_socket,
				(Sockaddr *)&client_addr,(socklen_t *)&addr_len);
	fputs("accepted ,done!\n\n\n",stdout);

	fputs("communication environment ready!\n\n",stdout);
	while(1){
		char test[BUFFSIZE];
		recv(client_socket,buffer,BUFFSIZE,0);
		if(strcmp(buffer,"out\n")==0)
			break;
		printf("client : ");
		fputs(buffer,stdout);
		putchar('\n');
		printf("send > ");
		fgets(buffer,BUFFSIZE,stdin);
		putchar('\n');
		send(client_socket,buffer,BUFFSIZE,0);
		if(strcmp(test,"out\n")==0)
			break;
	}
	fputs("communication finished!...out!\n",stdout);

	close(server_socket);

	return 0;
}

客户端:

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

#define SERVER_PORT 5555
#define BUFFSIZE 2048
#define ADDR_LEN sizeof(Sockaddr_in)
#define INET_ADDR "127.0.0.1"

typedef struct sockaddr_in Sockaddr_in;
typedef struct sockaddr Sockaddr;

int main(void){
	int client_socket;
	Sockaddr_in server_addr;
	char buffer[BUFFSIZE];

	if((client_socket=socket(AF_INET,SOCK_STREAM,0))<0){
		perror("socket");
		exit(1);
	}
	fputs("socket created!\n",stdout);

	bzero(&server_addr,ADDR_LEN);
	server_addr.sin_family=AF_INET;
	server_addr.sin_port=htons(SERVER_PORT);
	server_addr.sin_addr.s_addr=inet_addr(INET_ADDR);

	fputs("connecting...\n",stdout);
	if(connect(client_socket,(Sockaddr *)&server_addr,ADDR_LEN)<0){
		perror("connect");
		exit(1);
	}
	fputs("done!\n\n\n",stdout);

	fputs("communication environment ready!\n\n",stdout);
	while(1){
		printf("send > ");
		fgets(buffer,BUFFSIZE,stdin);
		putchar('\n');
		send(client_socket,buffer,BUFFSIZE,0);
		if(strcmp(buffer,"out\n")==0)
			break;
		recv(client_socket,buffer,BUFFSIZE,0);
		if(strcmp(buffer,"out\n")==0)
			break;
		printf("server : ");
		fputs(buffer,stdout);
		putchar('\n');
	}
	fputs("cmmunication finished!...out!\n",stdout);

	close(client_socket);

	return 0;
}

这两个程序主要实现连接后,由客户端和服务器端交互通信,任意一方发送out信息,则双方都断开连接,程序结束。

下面总结一下使用到的头文件:

  • stdio.h:标准输入输出头文件,本例中主要提供 printf(),fputs(),fgets(),putchar(),perror() 这些函数的声明。
  • stdlib.h:标准库头文件,本例中提供 exit() 的函数声明。
  • string.h:字符创头文件,本例中提供 strcmp() 的函数声明。
  • errno.h:之前以为它提供了 perror() 的函数声明,问了度娘才知道 perror() 的声明在 stdio.h 里。根据度娘的说法,该头文件                              为C标准函式库里的标头档,定义了很多错误码的宏。
  • unistd.h:提供 Linux 中 API 的访问功能。
  • sys/types.h:提供基本系统数据类型的访问功能(具体情况还不了解,一会自己去查一下)。
  • sys/socket.h:提供socket编程基本函数和一些结构体。
  • arpa/inet.h:提供 htons(),htonl(),inet_addr() 和 in_addr 的访问

 

下面总结一下socket编程中重要的结构体:

  • struct in_addr:
struct in_addr {
	union {
		struct {
			unsigned char s_b1, s_b2, s_b3, s_b4;
		}S_un_b;//An IPv4 address formatted as four unsigned chars
		struct {
			unsigned short s_w1, s_w2;
		}S_un_w;//An IPv4 address formatted as two unsigned shorts
		unsigned long S_addr;//An IPv4 address formatted as a unsigned long
	}S_un;
#define s_addr S_un.S_addr
};

该结构体用来储存32位 IPv4 地址。我们可以看到,在结构体中定义了一个联合体,用户可以以四种不同的形式储存数据。用的最多的(我感觉,其实我没有用过多少次。。。)是最后一个:unsigned long S_addr,但是平时在使用的时候,我们一般使用的s_addr,这是怎么回事?看结构体最后的宏定义吧,s_addr 会被 S_un.S_addr 替换掉,最后使用的还是联合体内的定义。

该结构体我觉得比较简单,就不进一步介绍了。

  • struct sockaddr_in:
struct sockaddr_in {
	short int sin_family;//Address family
	unsigned short int sin_port;//Port number
	struct in_addr sin_addr;//Internet address
	unsigned char sin_zero[8];
};

该结构体储存地址信息。短整型 sin_family 表示协议族,根据我所掌握的情况,只能用AF_INET,即TCP/IP协议族。无符号短整型 sin_port 表示端口号。下面的结构体就是我们上面讲的了,用来存储IPv4地址。最后的sin_zero[8] 只是为了使该结构体的字节大小和下面要讲的struct sockaddr相等,不用理会。

struct sockaddr 是通用的socket地址表示方法。度娘:“为了统一地址结构的表示方法 ,统一接口函数,使得不同的地址结构可以被bind()、connect()、recvfrom()、sendto()等函数调用。” 但在对地址信息进行操作时,一般不用该结构体,而使用sockaddr_in,在要将其用于参数时,强制转换为 struct sockaddr 类型。由于两个结构体都为16字节,所以可以互换。

以下是struct sockaddr 的内容,由于其将端口号和IPv4地址存储在一起,不方便使用,所以才出现了sockaddr_in 以弥补它的缺陷。

  • struct sockaddr:
struct sockaddr {
	unsigned short sa_family;//Address family
	char sa_data[14];//Protocol address
};

 

 

下面总结所使用到的一些函数:

 

  • socket

函数原型:int socket( int af, int type, int protocol);

头文件:sys/socket.h

参数:af 为地址描述,仅 AF_INET 可用;type 为 socket 类型,可用的类型有SOCK_STREAM(TCP)、                                                  SOCK_DGRAM(UDP)、SOCK_RAW(原始嵌套字)、SOCK_PACKET、SOCK_SEQPACKET;protocol 表示协                            议,不需要时可指定为0。

返回值:无错误时,返回新套接口的描述字。若错误,返回小于0的值。

  • bind

函数原型:int bind( int  sockfd , const struct sockaddr *my_addr , socklen_t addrlen );

头文件:sys/socket.h

参数:sockfd 表示一个未被绑定的套接口描述字;my_addr 指向存有socket地址的地址(注意:需要强制转换类                                    型!);addrlen 为前面指针指向结构体的大小。

返回值:无错误则返回0。否则返回一个负值。

  • listen

函数原型:int listen(int sockfd , int backlog);

头文件:sys/socket.h

参数:sockfd 表示一个捆绑而未连接的套接口的描述字;backlog 表示等待队列的最大值。

返回值:无错误则返回0,。否则返回一个负值。

  • accept

函数原型:SOCKET accept( int sockfd , struct sockaddr *addr , socklen_t *addrlen);

头文件:sys/socket.h

参数:sockfd 表示一个listen()过了的套接口的描述字;addr指向接收连接实体(本例中即为sockaddr)的地址;addrlen                        指向一个存有 addr 地址长度的整型数。

返回值:若无错误,返回SOCKET类型的值(即套接口描述字)。否则返回一个负值。

  • connect

函数原型:int connect( int sockfd , struct sockaddr *serv_addr , int addrlen);

头文件:sys/socket.h

参数:sockfd 为套接口描述字;serv_addr 指向结构体sockaddr,包含目的端口和IP地址;addrlen 为sockaddr的长度。

返回值:若无错误则返回0。否则返回一个负值。

  • send

函数原型:ssize_t send( int sockfd , const void *buf , size_t len , int flags);

头文件:sys/socket.h

参数:sockfd 为发送端套接口描述字;buf 指向待发送数据的缓冲区;len 表示实际发送数据的字符数;flags 一般置0。

返回值:若无错误则返回发送的字符数。否则返回一个负数。

  • recv

函数原型:ssize_t recv( int sockfd , void *buf , size_t len , int flags );

头文件:sys/socket.h

参数:sockfd 为接收端套接口描述字;buf 指向存储缓冲区;len 表示存储字符数;flags 一般为0。

返回值:若无错误则返回实际存储的字符数。否则返回一个负数。

 


编程中出现的问题:

  1. 在server代码中误将accept函数放入接受发送的循环中,而在client中connect函数位于循环之外,导致通信发生一个来回就被断开。度娘后得知,原因是客户端为长连接,服务器端为短连接。将两者匹配即可解决问题。
  2. 开始使用strcmp(buffer,"out"),却发现无论双方谁输出out都不能断开。度娘无果,甚是头疼。后来突然想到,之前用的puts函数自动去掉结尾的'\n',而因为用GCC编译使用puts会产生警告,遂换为fputs函数。而fputs函数保留结尾'\n',所以将判断改为strcmp(buffer,"out\n")即可解决问题。

总的来说,此次试验收获颇多。一方面了解了C/S运作原理,学习到了socket编程的基础,还加深了对几个常用函数的认识。

通过写博文,不失为一种强迫自己将模棱两可的知识弄清楚的方法,并且有助于巩固复习。

所以,坚持。

1、C/S结构,全C#版,即服务器、客户端全VS2010的C#编写; 相关办公系统(部署说明+qqimdb),另送qqsrc. 本用于政务网的即时通信的政务交流工具,界面上高仿QQ,包含完整客户端、服务器端、数据库源文件,全部C#编写,数据库采用SQL SERVER2008。系统采用TCP直连和服务器转发的综合设计,能穿透防火墙和不在线留言功能(当对方不在线或有防火墙时,通过服务器中转消息)。 主要功能: 1、注册、登陆 2、添加好友 3、聊天(可设置防止聊天,即沟通超过30条限制时间) 4、传送文件(可单传,批量传) 5、远程协助 6、快捷按钮功能(可设置常用功能) 等等 如果以上功能不能满足您的需求,我们可为您有偿定制开发新功能! 售后可帮助调试并给予开发指导和咨询,源码完整,可以此基础开发功能更加强大的网络通讯IM软件。 4.1.2 C/S结构,C#+DELPHI版,服务器C# vs2010,客户端DELPHI2007编写 2、C/S结构,C#+DELPHI版,服务器C# vs2010,客户端DELPHI2007编写,功能十分强大; icq全部源码打包 C/S结构,C#+DELPHI版,含所有源代码和技术文档。 如果以下功能不能满足您的需求,我们可为您有偿定制开发新功能!同时提供您现有系统、网站、OA等的数据库整合系统整合等服务。 4.1.3 3 C/S结构,全C++版,具有视频会议、白板、文件共享、即时通讯等功能。 3、C/S结构,全C++版,功能非常强大,具有视频会议、白板、文件共享、即时通讯等功能。 Gavcon视频会议系统带IM 4.1.4 4、C/S结构和B/S结构 4、C/S结构和B/S结构,全C#在线客服版,适用网站未注册客户在线咨询类服务。 精品C#即时通讯源码(商业版).zip
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值