linux下借助于套接字socket实现cs模型

下图展示的是OSI七层模型以及合并后更为实用的四层模型,其中我们常说的套接字socket编程基于传输层的,下面将介绍linux系统下实现一个简单的cs模型。

服务器端Server.c

创建套接字

进行套接字通信,那么最开始是要自己创建一个套接字,在linux系统中有这么个特点:一切皆文件,对于文件的操作都离不开一个fd(file descriptor)文件描述符,套接字也不例外,首先看看linux中提供的socket函数(查看linux开发手册):

SOCKET(2)                  Linux Programmer's Manual                 SOCKET(2)
NAMEsocket - create an endpoint for communication
SYNOPSIS#include <sys/types.h>          /* See NOTES */#include <sys/socket.h>int socket(int domain, int type, int protocol);DESCRIPTIONsocket()  creates  an endpoint for communication and returns a descriptor.The domain argument specifies a communication domain; this selects  theprotocol  family  which will be used for communication.  These familiesare  defined  in  <sys/socket.h>.   The  currently  understood  formatsRETURN VALUEOn  success,  a  file  descriptor  for  the new socket is returned.  Onerror, -1 is returned, and errno is set appropriately.

简言之,socket()函数需要传入三个参数:domain、type、protocol

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

其中第一个参数用于指明我们所用到的协议类型,这里为了实现稳定的tcp通信,传入AF_INET即可,表示我们选用IPv4网络协议。type为数据传输方式/套接字类型,常见的有SOCK_STREAM(流式套接字/稳定有连接的套接字)、SOCK_DGRAM(数据报套接字/无连接的套接字), 这里使用流式套接字SOCK_STREAM, 最后一个参数确定的是选用的协议,在此,TCP((Transfer Control Protocol)是流式套接字中的代表,可以传入 “0” 表示我们选用默认协议TCP,另外,数据报套接字代表协议是UDP(User Data Protocol)

int tcp_socket = socket(AF_INET, SOCK_STREAM, 0);  //创建TCP套接字
int udp_socket = socket(AF_INET, SOCK_DGRAM, 0);  //创建UDP套接字

当然也可以使用宏IPPROTO_TCP显式指明使用TCP协议:

int tcp_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);  //IPPROTO_TCP表示TCP协议

函数返回值为int类型,

2.绑定套接字

刚创建的socket只是一个确定了使用具体协议的空壳,要与计算机中某个进程进行通信还需要对套接字进行信息绑定,使用bind函数:

BIND(2)                    Linux Programmer's Manual                   BIND(2)NAMEbind - bind a name to a socketSYNOPSIS#include <sys/types.h>          /* See NOTES */#include <sys/socket.h>int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);DESCRIPTIONWhen  a  socket  is  created  with socket(2), it exists in a name space(address family) but has no address assigned to it.  bind() assigns theaddress  specified  by  addr  to  the  socket  referred  to by the filedescriptor sockfd.  addrlen  specifies  the  size,  in  bytes,  of  theaddress structure pointed to by addr.  Traditionally, this operation iscalled “assigning a name to a socket”.

sock为调用socket函数后返回的文件描述符,addr为sockaddr结构体变量的指针 addlen为addr变量的大小,可由sizeof()运算符计算得出。

int bind(int sock, struct sockaddr* addr, socklen_t addrlen);

在linux系统中的这些函数参数有这样的规律:

  • 参数前有const修饰表示参数为只读传参,也就是所谓传入参数,与之相对的就是传入传出参数。
  • 参数名后带有s,如: int* buffers,基本上(话不能说太满OvO~)表示传入的参数为一个数组。
  • 浏览一下 struct sockaddr 的结构信息:
 struct sockaddr {sa_family_t sa_family;char        sa_data[14];
};

在此处需要注意的是不能直接使用结构体struct sockaddr, 因为需要用IP地址和端口号与套接字绑定,对于端口号存在存储差异,在网络中端口号是大端存储,而本地端口号使用小端存储,什么是大端小端,有什么差异?
因此我们使用端口号之前必须通过转换,IP存储的格式为点分十进制,IPv4占用四个字节,每个字节对应的内容我们都需要写入到
sa_family参数需要填写的就是所使用的网络协议族: IPv4、IPv6,填入对应的宏即可(IPv4对应AF_INET,IPv6对应AF_INET6)。
第二个参数为一个数组sa_data[14];
我们在编写程序时并不使用struct sockaddr, 而是用sockaddr_in 代替它,
下图是 sockaddr 与 sockaddr_in 的对比(括号中的数字表示所占用的字节数):

sockaddr 和 sockaddr_in 的长度相同,都是16字节,只是将IP地址和端口号合并到一起,用一个成员 sa_data 表示。要想给 sa_data 赋值,必须同时指明IP地址和端口号,例如”127.0.0.1:80“,遗憾的是,没有相关函数将这个字符串转换成需要的形式,也就很难给 sockaddr 类型的变量赋值,所以使用 sockaddr_in 来代替。这两个结构体的长度相同,强制转换类型时不会丢失字节,也没有多余的字节。
可以认为,sockaddr 是一种通用的结构体,可以用来保存多种类型的IP地址和端口号,而 sockaddr_in 是专门用来保存 IPv4 地址的结构体。
正是由于通用结构体 sockaddr 使用不便,才针对不同的地址类型定义了不同的结构体。
注:这里对于 struct sin_addr s_addr 一般赋值为 宏INADDR_ANY, 表示该套接字的所有网络IP都可以进行通信。可以用于解决如下图的情形:

*图片截取自 慕课计算机网络(哈尔滨工业大学)课程

3.让套接字进入监听状态并相应客户端请求

对于服务器端程序,使用 bind() 绑定套接字后,还需要使用 listen() 函数让套接字进入被动监听状态,再调用 accept() 函数,就可以随时响应客户端的请求了。

listen()函数
int listen(int sock, int backlog);  //Linux

listen函数用法较为简单,传入之前创建好的套接字以及一个backlog数值,第一个参数表示需要进入监听状态的套接字,第二个参数确定了请求队列的最大长度。

请求队列:当套接字在处理客户端请求时,可能会有新的请求进来,这时候就会按照顺序排在请求队列中等待被处理,当队列满时客户端提出请求会收到 ECONNREFUSED 错误。
注意:listen() 只是让套接字处于监听状态,并没有接收请求。接收请求需要使用 accept() 函数。

accept()函数
ACCEPT(2)              Linux Programmer's Manual              ACCEPT(2)NAMEaccept, accept4 - accept a connection on a socketSYNOPSIS#include <sys/types.h>          /* See NOTES */#include <sys/socket.h>int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);#define _GNU_SOURCE             /* See feature_test_macros(7) */#include <sys/socket.h>int accept4(int sockfd, struct sockaddr *addr,socklen_t *addrlen, int flags);DESCRIPTIONThe  accept()  system  call is used with connection-based sockettypes (SOCK_STREAM, SOCK_SEQPACKET).  It extracts the first con‐nection request on the queue of pending connections for the lis‐tening socket, sockfd,  creates  a  new  connected  socket,  andreturns  a  new  file  descriptor referring to that socket.  Thenewly created socket is not in the listening state.  The  origi‐nal socket sockfd is unaffected by this call.

套接字进入监听状态后就可以处理客户端提出的请求了,accept()系统调用与基于连接的套接字一起使用(如SOCK_STREAM流式套接字、SOCK_SEQPACKET)。它提取第一个挂起连接队列上的连接请求, 创建了一个新的已连接的套接字,然后返回引用该套接字的新文件描述符。这个新创建的套接字未处于监听状态。原始套接字sockfd不受此调用影响。

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

addr 保存了客户端的IP地址和端口号,而 sock 是服务器端的套接字,addrlen是参数addr的占用空间大小

 //接收客户端的请求struct sockaddr_in clnt_addr;socklen_t clnt_addr_size = sizeof(clnt_addr);int clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, clnt_addr_size);
4.向套接字写入内容

accept() 返回一个新的套接字来和客户端通信,注意区分: 后面和客户端通信时,要使用这个新生成的套接字,而不是原来服务器端的套接字。
演示一下使用套接字向客户端写入信息:

 char str[] = "bincode.blog.csdn.net";write(clnt_sock, str, sizeof(str))

最后需要说明的是:listen() 只是让套接字进入监听状态,并没有真正接收客户端请求,listen() 后面的代码会继续执行,直到遇到 accept()。accept() 会阻塞程序执行(后面代码不能被执行),直到有新的请求到来。

演示程序

该程序是一个非常简单的回射服务器:客户端向套接字中写入内容,服务器从中读取出该信息并写回客户端。
serv.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>#define PORT 6666
int main(int argc, char* argv[]){int servSock = socket(AF_INET, SOCK_STREAM, 0);struct sockaddr_in serv_addr;memset(&serv_addr, 0, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_port = htons(PORT);serv_addr.sin_addr.s_addr = INADDR_ANY;bind(servSock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));listen(servSock, 10);struct sockaddr_in clnt_addr;socklen_t size = sizeof(clnt_addr);while(1){int telSock = accept(servSock, (struct sockaddr*)&clnt_addr, &size);//发送连接成功提示信息char inf[1024] = "Hello, client~\n";send(telSock, inf, sizeof(inf), 0);while(1){memset(inf, 0, sizeof(inf));int rr = recv(telSock, inf, 1024, 0);printf("serv收到信息:%s\n", inf);if(inf[0] == '0') break;//如果收到首字符为0,则退出当前循环,继续监听下一个套接字send(telSock, inf, rr, 0);//将信息发回客户端}}return 0;
}

clnt.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>#define PORT 6666
int main(int argc, char* argv[]){int clntSock = socket(AF_INET, SOCK_STREAM, 0);struct sockaddr_in clnt_addr;memset(&clnt_addr, 0, sizeof(clnt_addr));clnt_addr.sin_family = AF_INET;clnt_addr.sin_addr.s_addr = inet_addr("127.0.0.1");clnt_addr.sin_port = htons(PORT);connect(clntSock, (struct sockaddr*)&clnt_addr, sizeof(clnt_addr));while(1){char buf[1024] = { 0 };int rr = recv(clntSock, buf, 1024, 0);printf("服务器回射信息:%s\n", buf);//从服务器接收信息memset(buf, 0, sizeof(buf));//清空buf中的信息,为了向服务器输入信息printf("请输入信息:\n");scanf("%s", buf);send(clntSock, buf, sizeof(buf), 0);if(buf[0] == '0')break;}close(clntSock);//关闭套接字return 0;

运行效果

上半部分为服务端serv,下半部分为客户端clnt。
步骤说明:

  1. 启动服务端: ./serv
  2. 启动客户端: ./clnt
  3. 客户端接收到信息: Hello, client~
  4. 客户端输入信息: hello,server
  5. 服务端接收到信息,并将信息写回客户端
  6. 客户端接收到信息,读出并将其打印到shell
  7. 最后客户端输入 0,表明要结束本次连接
  8. 服务端接收到0,进入下一轮循环,继续阻塞等待其他的套接字与之连接

实现CS客户端服务器模型(Linux系统)相关推荐

  1. Synergy工具 共享Windows系统电脑Linux系统电脑 鼠标键盘

    Synergy工具 共享Windows系统电脑Linux系统电脑 鼠标键盘 相关软件相关文章发表评论 来源:西西整理时间:2014/11/6 15:03:11字体大小:A-A+ 作者:西西点击:24评 ...

  2. linux多点触摸屏驱动程序,Linux系统实现支持多点触控操作[视频]

    虽然大多数人目前还不会购买拥有触控屏的设备,不过在Windows 7的大力推动下,触控操作已经开始流行了起来.然而Linux目前却无法支持这一功能,这让很多用户十分郁闷,不过来自ENAC Intera ...

  3. windows linux 共享鼠标,Synergy工具 共享Windows系统电脑Linux系统电脑 鼠标键盘

    Synergy1.5.0 免费英文版 类型:行业软件大小:7.1M语言:英文 评分:.9 标签: 立即下载 Synergy是个开源的软件,支持在一个局域网中的多台电脑之间共享鼠标键盘,支持Window ...

  4. Linux的shutdown关机命令,Linux系统Shutdown命令定时关机详解

    转自:http://www.bootf.com/490.html Linux系统下的shutdown命令用于安全的关闭/重启计算机,它不仅可以方便的实现定时关机,还可以由用户决定关机时的相关参数.在执 ...

  5. linux nfs系统客户端,Linux系统中挂载共享目录NFS文件系统客户端安装与配置

    NFS服务简介      NFS是Network  File System(网络文件系统).主要功能是通过网络让不同的服务器之间可以共享文件或者目录.NFS客户端一般是应用服务器(比如web,负载均衡 ...

  6. linux可用机场客户端,Linux系统可用的6款Bittorrent客户端

    大家都知道迅雷目前尚不支持Linux系统,其实使用Bittorrent客户端进行下载未尝不是一个好的选择,这里给大家介绍6款Linux可用Bittorrent客户端,方便经常需要进行文件下载的Linu ...

  7. vnc远程控制软件,linux系统如何使用vnc远程控制软件,vnc客户端使用教程

    VNC作为一个优秀的远程控制软件,一直很受运维和站长等工作人员的喜欢.对于这样的一个软件,很多时候下载就成为了稍微复杂的问题.但如果使用IIS7服务器管理工具则要简单很多,它可以作为vnc的客户端,进 ...

  8. linux系统能看抖音吗,字节跳动应该推出抖音/TikTok for Linux版本客户端

    字节跳动的抖音/TikTok和Linux开源技术非常的密切,所以很多用户也希望字节跳动能够推出真正的抖音/TikTok for Linux版本客户端,当前腾讯的QQ和微信都可在Linux平台上使用. ...

  9. LINUX系统下ORACLE19C客户端安装步骤

    LINUX系统下ORACLE19C客户端安装步骤 服务器系统版本:CentOS 7.4 Oracle客户端安装包(19C版本)下载地址: Instant Client for Linux x86-64 ...

  10. Linux系统最受欢迎的邮件客户端

    邮件客户端 也被称为邮件读取软件或邮件收发软件,是电子邮件系统中的一种应用程序,用于管理.读取和发送电子邮件.它运行在个人计算机或移动设备上,并通过网络与邮件服务器进行通信.邮件客户端通常包括以下功能 ...

最新文章

  1. python数据转换函数_常用python数据类型转换函数总结
  2. 用javascript进行一个简单的机器学习小实例
  3. DOCTYPE声明对JS获取窗口宽度和高度的影响【转】
  4. ECMAScript 6 未来前景
  5. Java 技术篇-借助自定义对象实现函数返回多个不同类型的值实例演示
  6. python知识:如何从图片的四周扩大一些尺寸
  7. java filereader blob_二进制学习——Blob,ArrayBuffer、File、FileReader和FormData的区别
  8. java==和=_java中==和 equal区别
  9. [Poj 1459] 网络流(一) {基本概念与算法}
  10. 疯子的算法总结(五) 矩阵乘法 (矩阵快速幂)
  11. python socket send_python socket 连续send,出现粘包问题
  12. css 浮动效果 0302
  13. 深度学习之TensorFlow
  14. ERR Errors trying to SHUTDOWN. Check logs.
  15. 狂神说Java--Java学习笔记(合集)
  16. ZebraDesigner3 打印到.prn文件乱码
  17. 省钱兄(APP、h5版本)任务悬赏点赞平台uniapp前端源码模板
  18. Mac将本地文件上传到服务器以及从服务器下载文件到本地
  19. line 1 appears to contain embedded nulls
  20. 态密度的Delta函数公式以及范霍夫奇点

热门文章

  1. python问题——ValueError: only 2 non-keyword arguments accepted
  2. 谈谈我职业生涯中的三次潦倒 Leo病中的思考 续
  3. Python Day05习题
  4. 优秀测试工程师应该具有的基本素质
  5. CSS div斜线倾斜45度
  6. 神经网络容易受到对抗攻击,网络攻防原理与技术
  7. centos8 配置DNS服务
  8. 智能家居时代到来?智能家居是有必要的吗?
  9. 前端接收pdf文件_前端实现PDF导出功能
  10. mysql聚簇索引和非聚簇索引的区别_聚簇索引与非聚簇索引的区别