700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > 我的第一篇博文——简单的C/S模型

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

时间:2021-04-07 17:40:36

相关推荐

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

这几天在学习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 charsstruct {unsigned short s_w1, s_w2;}S_un_w;//An IPv4 address formatted as two unsigned shortsunsigned 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 familyunsigned short int sin_port;//Port numberstruct in_addr sin_addr;//Internet addressunsigned 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 familychar sa_data[14];//Protocol address};

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

socket

函数原型:intsocket( 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_tsend( int sockfd , const void *buf, size_t len , int flags);

头文件:sys/socket.h

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

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

recv

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

头文件:sys/socket.h

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

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

编程中出现的问题:

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

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

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

所以,坚持。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。