socket编程:简单TCP服务器/客户端编程
其实对于socket:我们需要理解的是他提供了一种编程概念,利用socket就可以利用系统已经封装好的内部进行通信,我们只需要关注应用层方面的数据控制就OK了。
一. 套接字(socket)
socket英文为插座的意思,也就是为用户提供了一个接入某个链路的接口。而在计算机网络中,一个IP地址标识唯一一台主机,而一个端口号标识着主机中唯一一个应用进程,因此“IP+端口号”就可以称之为socket。
两个主机的进程之间要通信,就可以各自建立一个socket,其实可以看做各自提供出来一个“插座”,然后通过连接上“插座”的两头也就是由这两个socket组成的socket pair就标识唯一一个连接,以此来表示网络连接中一对一的关系。
我们先来简单的了解一下socket究竟是什么东东。
首先对于套接字编程,他需要指定的套接字的地址作为参数,所以在网络协议中存在不同的协议,即存在不同的地址结构定义方式。对于这些结构都是sockaddr_开头的。每一个协议有一个唯一的后缀,例如以太网的就是sockaddr_in,这个常用。
然后我们来看一下通用的套接字结构:
struct sockaddr{sa_family sa_family;char sa_data[14];
}
这个是套接字的原型,注意在套接字编程中,关于sockaddr_的函数都需要进行类型转换转换为sockaddr.
以太网中常用的套接字结构是:
对应关系是:
sin_family:对应的是地址类型:AF_INET代表ipv4。
sin_port:代表端口号。
sin_addr.s_addr:代表我们所建立的ip地址。
在编程之前,我们需要关注的是,在计算机中,字节序的存储分为大端小段,在网络字节序中,利用的是大端状态。而计算机可能存在大端可能存在小端,所以就存在一些字节序的转换函数:
就如函数名一样。host字节序to转换为net字节序l long4字节长度,余下函数同理。
然后我们还需要关注的是,在我们的sockaddr_in中,ip地址存在sin_addr.s_addr的类型,字符串的点分十进制的类型,还有二进制的类型,所以就有一系列的ip地址结构转换函数:
根据这几个函数的输入参数和输出参数可以看到他们的转换时从什么转换到什么,
然后还有2个安全转换的函数:
这2个函数是针对不同协议族的地址转换,第一个参数就代表网络类型协议族。
在基于上面的了解情况下,我们了解一下socket基于Tcp协议实现可靠传输的连接传输释放过程:
然后我们需要注意一下几个问题:
客户端与服务器的交互过程:
客户端的连接过程,对服务端是接收过程。然后在过程中进行3次握手建立TCP连接。
客户端与服务端之间的数据交换是相对的过程,客户端的读数据对应的是服务器端的写数据过程。客户端的写数据对应服务器的读数据过程。
在交互完毕后,关闭套接字连接。
下面开始套接字编程的认识。
首先我们来看一下代码,然后进行讲解:
首先来看服务端的代码:
#include<stdio.h>
#include<unistd.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<string.h>
#include<error.h>
#include<arpa/inet.h>#define _PROT_ 8888
#define _BACKLOG_ 5void process_conn_server(int s)
{ssize_t size = 0;char buffer[1024];while(1){size = read(s,buffer,1024);if(size == 0){return ;}sprintf(buffer,"%d bytes altongether\n",size);write(s,buffer,strlen(buffer) + 1);}
}int main()
{int ss,sc;struct sockaddr_in server_sock;struct sockaddr_in client_sock;pid_t pid;int err;ss = socket(AF_INET,SOCK_STREAM,0);if(ss < 0){printf("sock build error");return 1;}bzero(&server_sock,sizeof(server_sock));server_sock.sin_family = AF_INET;server_sock.sin_addr.s_addr = htonl(INADDR_ANY);server_sock.sin_port = htons(_PROT_);err = bind(ss,(struct sockaddr *)&server_sock,sizeof(server_sock));if(bind < 0){printf("bind error");return 2;}err = listen(ss,_BACKLOG_);if(err < 0){printf("listen is error");return 3;}while(1){socklen_t len = sizeof(struct sockaddr);sc = accept(ss,(struct sockaddr *)&client_sock,&len);if(sc < 0){continue;}pid = fork();if(pid == 0){process_conn_server(sc);close(ss);}else{close(sc);}}return 0;
}
对于这些代码,首先来了解一下服务器要是先套接字所需要进行的函数调用过程,然后在来讨论一些效率和安全性的问题。
首先我们需要创建网络插口函数socket():
domain设置网络通信域,在以太网中使用AF_INET。
type是设置套接字通信的类型,TCP是面向字节流传输,所以使用SOCK_STREAM。
protocol通常设置为0;
对于这个函数而言,他就是为了我们的通信而打开一个文件描述符,调用成功就返回文件描述符方便数据传输,失败就返回-1.同时传出错误值。
在socket编程中的这一套函数中,都进行了底层的封装,提供了相对的借口,
我们来看一下socket()的内核实现:
用户调用socket()后,
系统调用sys_socket,其中
生成内核socket结构。
与文件描述符绑定。将绑定文件描述符值传给应用层。
当建立套接字文件描述符成功后,就需要对套接字进行地址和端口绑定,这时候就是bind()函数,同时我们需要创建一个sockaddr_in 的结构体来进行绑定。
来看一下函数:
第一个参数就是我们调用socket所返回的文件描述符,第二个就是我们所创建的sockaddr_in结构体,当然,我们传参数的时候我们需要进行强制类型转换,第3个参数是我们所设置结构体的长度,同时也是输入数据,也是输出数据。
当bind返回0表示绑定成功,返回-1表示绑定失败。
然后我们还是看一下他的内核调用:
然后就可以进入监听状态,函数listen()用来初始化服务器可连接队列,服务器统一时间仅能处理一个客户端连接,当多个客户端的连接请求同时到来的时候,需要排入等待队列,一个一个处理。
listen函数:
sockfd代表文件描述符,
backlog代表等待队列的长度,成功返回0,失败返回-1.
listen内核:
图片出自Linux网络编程。
然后当我们设置监听理由就是accept()等待连接,然后我们连接成功后就会返回一个新的套接字文件描述符来表示客户端的连接,客户端连接的信息可以由这个新的描述符获得,然后可以通过write和read来实现数据传送。
看一下accept()函数:
函数参数中,
sockfd是创建的一个socket,这个socket是和listen用同一个socket,因为是送监听处得到请求连接;
addr是用于描述请求连接一方的网络地址信息结构体的指针;
addrlen是上述结构体的大小;
函数成功会返回有效的接收到的socket描述符,失败返回-1并置错误码;
内核调用:
好了这就是我们的服务端,他连接建立。
下面我们来看一下客户端的编写,代码:
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<error.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>#define _PORT_ 8888void process_conn_client(int s)
{ssize_t size = 0;char buffer[1024];while(1){size = read(0,buffer,1024);if(size > 0){write(s,buffer,size);size = read(s,buffer,1024);write(1,buffer,size);}}
}int main()
{int s;struct sockaddr_in server_sock;s = socket(AF_INET,SOCK_STREAM,0);if(s < 0){printf("sock error");return 1;}bzero(&server_sock,sizeof(server_sock));server_sock.sin_family = AF_INET;server_sock.sin_addr.s_addr = htonl(INADDR_ANY);server_sock.sin_port = htons(_PORT_);connect(s,(struct sockaddr *)&server_sock,sizeof(server_sock));process_conn_client(s);close(s);return 0;
}
对于客户端而言,我们只需要连接上我们的服务端就好了,所以只需要关注一个函数,connect函数,然后我们看一下connect()函数:
函数参数中,
sockfd是连接方创建的一个socket文件描述符;
addr因为是连接请求方,所以是远端要接收连接请求一方的网络socket地址信息;
addrlen是上述网络地址信息结构体的大小;
函数成功返回0,失败返回-1并置错误码;
他的内核实现:
对于客户端和服务端而言,我们的编程已近实现了,然后我们需要考虑的是几个关于服务端的性能问题:
在我所编写的代码中,我采用的是多进程的方式,每当我们建立连接后就fork一个子进程,子进程中进行数据传输,然后我们父进程就关闭accept所产生的文件描述符,子进程关闭不需要的监听的文件描述符。
在多进程中,我们需要注意到的是我们使用的是阻塞式的I/O模型,我们父进程中如果调用wait函数,waitpid函数进行等待总会比较浪费资源效率。
而且对于waitpid的非阻塞等待还存在一个问题,当我们需要释放子进程资源的时候,但是出现以下场景:
一个客户端连接进来,然后父进程进行非阻塞的等待,但是若以后都没有连接,将会卡在accept处,导致僵尸进程的产生。所以这种方式是存在问题的,所以有一种解决办法,就是利用子进程结束时返回的SIGCHLD信号来捕捉进行自定义的子进程资源释放,
还有一种方式就是多线程的编程,调用线程为分离状态。
pthread_t tid;//创建出一个线程pthread_create(&tid, NULL, accept_fun, (void *)accept_sock);pthread_detach(tid);//将线程设置成分离状态,结束后不必等主线程回收资源
void* accept_fun(void *sock)
{int accept_sock = (int)sock;char *buf[1024];while(1){memset(buf, '\0', sizeof(buf));size_t size = read(accept_sock, buf, sizeof(buf)-1);if(size < 0){perror("read");break;}else if(size == 0){printf("client is out...\n");break;}elseprintf("client# %s\n", buf);}}
当然了,无论是多线程方式还是多进程方式,他总会出现性能上的低效率,因为一个线程/进程只能够处理一个连接的话是十分低效的,所以我们需要继续学习关于I/O的几种模型,使用多路复用状态的模型去进行服务器端的编写。
转载于:https://blog.51cto.com/memory73/1775694
socket编程:简单TCP服务器/客户端编程相关推荐
- TCP服务器客户端编程流程
TCP服务器客户端编程流程 TCP编程流程 主机字节序列和网络字节序列 套接字地址结构 通用socket地址结构 一般使用会定义一个专用的套接字结构 IP地址转换函数 网络编程接口 TCP服务段代码实 ...
- [linux][c语言]用socket实现简单的服务器客户端交互
Socket解释: 网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket. Socket的英文原义是"孔"或"插座".作为B ...
- Socket网络编程--简单Web服务器(6)
本来是想实现ssl连接的,但是弄了好久都不成功,就索性不做了,等以后有能力再做了.所以这一小节就是本次的最后一节了.就简单的说几个注意点. 1.加个配置文件 使用单例模式,使用一个类,该类保存一些信息 ...
- UDP服务器客户端编程流程
UDP服务器客户端编程流程 UDP编程流程 UDP服务端代码实现 UDP客户端代码实现 UDP服务端客户端代码详解 UDP编程流程 UDP提供的是无连接.不可靠的.数据报服务 UDP是尽最大能力进行传 ...
- Qt的Tcp服务器多线程编程-附带代码展示
Qt的Tcp服务器多线程编程-附带代码展示 该程序主要实现tcp服务器如何使用多线程的方式来连接多个客户端,此文章没有实现客户端的多线程编程. 创建子线程时需要注意的点: 1.子线程与主线程之间交互数 ...
- 5005.boost之asio简单反射型服务器客户端
5005.boost之asio简单反射型服务器客户端 参考boost库完全开发和网友代码,仅此记录方便后续查询 服务器端 tcp_server.cpp /*********************** ...
- Windows Socket 编程_ 简单的服务器/客户端程序 .
一.程序运行效果图 二.程序源代码 三.程序设计相关基础知识 1.计算机网络 2.IP地址 3.协议 4.网络体系结构 5.TCP/IP体系结构与特点 6.客户 ...
- Windows Socket 编程_ 简单的服务器/客户端程序
一.程序运行效果图 二.程序源代码 三.程序设计相关基础知识 1.计算机网络 2.IP地址 3.协议 4.网络体系结构 5.TCP/IP体系结构与特点 6.客户 ...
- 【Java 网络编程】TCP 服务器端 客户端 简单示例
文章目录 I IntelliJ IDEA 创建 Java 项目 II 客户端 Socket 创建 III Socket 客户端连接服务器端 IV Socket 两个端点信息获取 V 控制台人机交互 V ...
- Linux 高级I/O之poll函数及简单服务器客户端编程
当需要同时监听多个文件描述符时,就需要I/O复用函数,I/O复用函数有select.poll.epoll,今天主要使用poll函数. poll()接受一个指向结构'struct pollfd'列表的 ...
最新文章
- iphone通讯录批量删除_iPhone通讯录删除了如何恢复?用对方法快速找回,亲测有效!_...
- ICCV 2021 | 基于稀疏多视角彩色相机的多人人体全身捕捉
- AAAI 2020 Oral论文--TANet:提升点云3D目标检测的稳健性
- ubuntu 安装opencl失败笔记
- ITK:查看矢量图像的分量
- 深入理解编译优化之循环展开和粗化锁
- 【转】UML基础: 第 2 部分 - 对象图 (Object Diagram)
- Android平台使用Camera2(5.0+)替代过时的Camera
- php的布局设计,php学习之div+css布局设计排版(六)
- SQL去除数据库表中tab、空格、回车符等特殊字符的解决方法
- poj 3666 河南省第七届程序设计D题(山区修路)
- 2019美赛A题翻译
- java回调函数(callBack)
- 误删除文件怎么才能恢复
- Spring 学习笔记(day02)
- 为什么内存为什么是以字节为单位的?
- AES AES/GCM/NoPadding 加密解密
- Pyecharts3D图:常见的3D图
- 计算机网络安全所学知识大纲,计算机网络安全课程教学大纲.doc
- 唯样商城:100年前的科技脑洞到底有多大?(第一个已经实现)