这几天在学习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

函数原型: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编程的基础,还加深了对几个常用函数的认识。

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

所以,坚持。

我的第一篇博文——简单的C/S模型相关推荐

  1. 测一下markdown之第一篇博文

    自从一直在用的互联主机接二连三的当机(使用过程中也是经常出问题)之后,终于怒删网站上所有的文章,竟然也未曾备份.所以,突然感觉,搭自己的个人博客站点,实在有点不讨好.或者说,遇人不淑,碰到了如此可恨的 ...

  2. 系统崩溃,u盘重装win7(第一篇博文)

    第一次打算写博文吧!好歹也是计算机系的,所以在这里写下我的第一篇博文,把自己学习生活中的感悟记录下来! 想想肚子也没多少文笔,就写前几天帮人装系统的经历吧! 1.下载win 7 sp1全新安装版,我的 ...

  3. 我在OSC的第一篇博文

    2019独角兽企业重金招聘Python工程师标准>>> 这是我的第一篇博文,以后会经常在这里写文章的. 转载于:https://my.oschina.net/moonshadow/b ...

  4. 自学IT后的第一篇博文

    这是我的第一篇博文,虽然不知道具体写点什么,但还是想记下一点东西.今天是一个开始,要把自己在以后的工作学习道路上的学到的新技能,遇到困难和解决的方法都记录下来.最后一句:"希望自己可以成长的 ...

  5. 我的第一篇博文——写下我的规划

    前言 我的故事 我是一个大学生,第一次来到CSDN是通过我的一个同学在群里分享的他的博文,我也是因为他的这个分享而开始认识他和他熟悉起来(现在我把他亲切的叫做师哥,他也叫我小师妹). 之所以和他认识和 ...

  6. 继2021-02-09第一篇博文后

    继2021-02-09第一篇博文后 所爱 目前的实现与掌握 未来要做的 do 大道无形,上下求索 所爱 军事联盟 夏洛克 琅琊榜 目前的实现与掌握 熟练使用Java 熟练使用MySQL.Redis,了 ...

  7. 第一篇博文——与诸位共勉

    第一篇博文--与诸位共勉 之所以决定维护一个个人博客,是因为它能够在方便他人的同时也能督促自己及时总结学习过的知识,在之后呢,我会每周发表一至两篇博文用于对学习的知识梳理总结,衷心的希望能贡献自己微薄 ...

  8. 第一篇博文——我的第一枚脚印

    第一篇博文--我的第一枚脚印 这是我第一次写博客,我将会在未来分享一些我学习编程的感悟.希望你们能与我一起成长,既然已经踏上旅途,那么祝愿我们能够一起携手同行. 个人简介 大一萌新,21级软件技术专业 ...

  9. csdn第一篇博文 关于国内互联网发展的感叹(今后望这儿的氛围更好,结识各行各业志趣相投的朋友)

    本人现今是一位大学学生,逛过或者说曾经混迹过各种贴吧/论坛,有过QTCN之类的技术性论坛,也有MAXPDA之类数码产品论坛,也有天涯,百度贴吧. 但是,我觉得都有一个现象,就是过于浮躁了.对于一件事的 ...

最新文章

  1. 自写网站阶段之:终结篇
  2. 6、Power Map—实例:柱状图按月展示数据变动
  3. 关于windows内存泄露思考
  4. 【机器学习基础】数学推导+纯Python实现机器学习算法22:最大熵模型
  5. [原]Java程序员的JavaScript学习笔记(12——jQuery-扩展选择器)
  6. python 虚拟环境原理_Python 虚拟环境
  7. 查询SQLSERVER执行过的SQL记录(历史查询记录)
  8. BUG--tomcat更改目录失败
  9. FileSync文件同步更新工具
  10. Java中的for循环和JavaScript中的for循环差别初探(02)
  11. 架构组件—Android应用中使用视图绑定(binding)
  12. Power算法求X的N次幂
  13. Python如何换pip的源(阿里云或清华云等源)
  14. 10个免费音效素材下载网站,下载超方便还能免费商用!
  15. matlab信息隐藏和提取,使用matlab进行图像信息隐藏和提取
  16. AutoSAR开发的三种方法:自上而下式、自下而上式、混合式
  17. 如何优雅的使用DbContext
  18. html-day13渐变动画
  19. 一个常见的文字无缝滚动效果
  20. 当当李国庆谈“刘强东案”:虽煞风景,但划得来

热门文章

  1. uni-app调用百度语音播报
  2. 看顶级渣男如何邀约100个女朋友(二)
  3. Linux安装配置ssh 基于unbantu22.04.1 LTS版本
  4. Netty入门与实战:仿写微信IM即时通讯系统
  5. 微信小程序换行符↵转义
  6. 使用Vue写一个登录页面
  7. Unity 3D 游戏与编程
  8. SCAPE、BlendSCAPE、SMPL、SMPL-H、SMPL-X、STAR等都是什么?请分别仔细介绍一下
  9. docker修改配置文件之后,导致不能启动
  10. 中文文本分类 传统机器学习+深度学习