linux下socket编程详解

来源:互联网 发布:网络星河 pdf 编辑:程序博客网 时间:2024/05/24 06:31


学习网络编程之前,我们首先要学习知道网络的一个重要的知识点,OSI模型。

OSI模型,即开放式通信系统互联参考模型。是国际标准化组织提出的一个试图使各种计算机在世界范围内互连为网络的标准框架,简称OSI。

OSI总共把网络分为7个层次即:物理层,数据链路层,网络层,传输层,会话层,表示层,应用层。

物理层: 将数据转换为可通过物理介质传送的电子信号 相当于邮局中的搬运工人
数据链路层: 决定访问网络介质的方式,在此层将数据分帧,并处理流控制。本层 指定拓扑结构并提供硬件寻 址。相当于邮局中的装拆箱工人
网络层: 使用权数据路由经过大型网络 相当于邮局中的排序工人
传输层: 提供终端到终端的可靠连接 相当于公司中跑邮局的送信职员
会话层: 允许用户使用简单易记的名称建立连接 相当于公司中收寄信、写信封与拆信封的秘书
表示层: 协商数据交换格式 相当公司中简报老板、替老板写信的助理
应用层: 用户的应用程序和网络之间的接口

 本文要讲解的socket是一种支持多种协议,工作在运输层和应用层的一种机制。在运输层通过TCP/UDP为应用层提供数据的传输。可以理解为是运输层和应用层中间的一种抽象层。

什么是TCP/IP UDP?

TCP/IP  是传输控制协议/因特网互联协议。TCP负责发现传输的问题,一有问题就发出信号,要求重新传输,直到所有数据安全正确地传输到目的地。而IP是给因特网的每一台联网设备规定一个地址,像是自己家里的电话号码一样。

UDP是面向无连接的通讯协议,UDP数据包括目的端口号和源端口号信息,由于通讯不需要连接,所以可以实现广播发送。UDP通讯时不需要接收方确认,属于不可靠的传输,可能会出现丢包现象,实际应用中要求程序员编程验证。
UDP与TCP位于同一层,但它不管数据包的顺序、错误或重发。
因此总结 UDP是一种不安全,没有建立初始化连接协议,TCP是一种安全,有初始化连接的协议。TCP是利用三次握手,来进行初始化连接。保证数据安全可达。
网络编程还有一个重要的概念是:端口。
什么是端口。
我们想象下,一个计算机上有很多的应用程序是需要和外部通信,并且这些应用程序是利用同一个ip和网卡的,那么数据进入到网卡的时候,是怎么更好的区分达到哪个应用程序呢,这里就需要端口的概念,计算机上为每一个需要和外部通信的程序分配了一个独一的端口,因为端口是无符号的整型,所以只能支持0到65535个,并且0到1024已经被操作系统的一些应用程序给利用,程序员所能利用的就是剩下的端口。
好了,有了以上的基本概念,我们就开始进行socket编程代码解析了。
我把这块分成两部分讲解,第一TCP编程,第二UDP编程。因为UDP是比较容易理解的,所以由浅入深,我们先看看UDP编程是如何实现的。
UDP编程。
网络编程有服务端和客户端的概念,其实服务器端和客户端两个流程之间的主要差别在于对地址的绑定函数(bind()函数),而客户端可以不用进行地址和端口的绑定操作。所以我们主要讲解服务端的,客户端的参考服务端就可以很好的理解。
第一:创建套接字。
我们利用int socket(int domain,int type,int );函数进行创建套接字socket,套接字是一种文件句柄,一旦创建成功,该文件句柄就会是本操作系统的唯一,所以我们做并发服务端编程的时候,也要考虑并发的时候文件句柄能开启的数量。
详细参数介绍: int domain 用于指定创建套接字的时候,使用的协议族。常见的协议族有AF_UNIX:创建本机内进行通信的套接字。AF_INET:使用IPV4 TCP/IP协议。AF_INET6:使用IPV6 TCP/IP协议。服务端和客户端需要通信,那就要选定相对应的通信协议。协议族决定了socket的地址类型,在通信中必须采用对应的地址类型。如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址
int type 指定socket类型,流式套接字SOCK_STREAM,数据报套接字SOCK_DGRAM,原始套接字SOCK_RAW。并不是所有的传输协议都支持这里的所有类型,所以我们需要对应好。原始套接字可以读写内核没有处理的IP数据包,而流套接字(就是TCP流)只能读取TCP协议的数据,数据包套接字只能读取UDP协议的数据。因此,如果要访问其他协议发送数据必须使用原始套接字。
int protocol  故名思意,就是指定协议。常用的协议有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议。一般我们给protocol赋上0,它会给我们选择好传输协议类型对应type的。
第二:绑定创建的套接字。
当我们创建一个套接字的时候,这个套接字没有一个具体的地址,存在于协议族空间中,所有我们利用函数bind给这个套接字赋上一个具体的地址,否则可调用connect,listen时候,系统会自动的分配一个端口。
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
详细参数介绍:int sockfd 一个套接字,就是我们用socket创建的套接字,我们用bind函数,就是将这个套接字和一个地址进行绑定。
const struct *addr 个const struct sockaddr *指针,指向要绑定给sockfd的协议地址。这个地址结构根据地址创建socket时的地址协议族的不同而不同,如ipv4对应的是:struct sockaddr_in 而ipv6对应的是 struct sockaddr_in6。unix域对应的是struct sockaddr_un。
socklen_t addrlen:对应的是地址的长度。
我们做服务端编程的时候,使用bind函数将一个ip和端口绑定在一个socket上面,这个时候这个ip和端口就可以固定的提供相应的服务了,然后客户端不用指定自身的ip和端口,只需要在connect的时候,指定服务端的ip和端口,自身的ip和端口connect的时候,会 自动的分配,不用客户端程序来写明。
第三:UDP编程下,我们绑定成功后,那就只需要一个循环去不停的write和read这个套接字了。有好几组I/O操作的函数,read()/write() ,recv()/send() ,readv()/writev(),recvmsg()/sendmsg() ,recvfrom()/sendto()等。可根据自身的习惯选择使用的函数
第四:关闭这个连接,关闭套接字。
我们使用close 关闭这个套接字,这个函数很简单原型是intclose(int fd);只需要将创建好的套接字赋上去就可以。
编程实例服务端:
 
#include<sys/socket.h>#include <sys/un.h>#include<sys/types.h>#include<netinet/in.h>#include<stdio.h>#include<stdlib.h>#include<unistd.h>#include<string.h>#include <signal.h>int fd;void fa(){        close(fd);        exit(0);}int main(int argc,char* argv[]){        int flag=1;        signal(SIGINT,fa);//给一个信号,触发的时候,关闭socket        fd=socket(AF_UNIX,SOCK_DGRAM,0);//创建一个socket,第一个协议族决定了该socket是在本机通信,所以我们这里用不到端口和ip        if(fd<0)        {                perror("socket:");                exit(0);        }        struct sockaddr_un addr_serv;//这里因为是在unix本机上进行通信,所以我们用sockaddr_un        memset(&addr_serv,0,sizeof(struct sockaddr_in));        addr_serv.sun_family=AF_UNIX;        strcpy(addr_serv.sun_path,"a.sock");//需要赋上一个产生socket通信的文件路径,这里在程序执行的目录下进行,那么所有的客户端就需要加载这个socket文件了        if(bind(fd,(struct sockaddr*)&addr_serv,sizeof(addr_serv))<0)//绑定这个套接字,也就是给这个套接字赋上需要的值,绑定成功后,就会在本目录下面生成一个a.sock文件,以保证其它客户端能进行相应的通信        {                perror("bind");                exit(1);        }        while(1)        {                char recv_buf[1024];//接收数据用的缓冲区                if(read(fd,recv_buf,sizeof(recv_buf))<0)//函数返回接收到的数据个数,如果返回负数,则失败                {                        perror("read:");                        exit(1);                }                printf("%s\n",recv_buf);//打印出来,接收到的数据        }}

 TCP编程
因为TCP涉及到一个三次握手机制,所以和UDP编程的不同之处,在于多了listen和accept函数的使用。
套接字的监听:listen函数
函数的原型是:int listen(SOCKET sockfd, int backlog);sockfd是创建套接字时候的socket的,这里使用这个函数让这个socket从主动连接,变成被动的去接收客户端的socket连接。后面的参数backlog就是被动连接时候最大的等待连接数,这里强调的是等待连接数,不是该服务端最大的连接数,也就是当有一些socket连接请求过来的时候,服务端先产生一个队列去存放这些连接请求的socket,这里相当于这些个请求是半连接状态,然后当这些请求个数达到了backlog的值时候,服务端就会先拒绝再来的请求,一直到服务端处理了这个请求,使其请求数小于backlog的时候,再次接受客户端的请求。函数失败返回非0,成功返回0
 套接字的接受:accept函数
TCP编程下前面所有的socket,bind,listen都是给相应的套接字赋上需要的属性,那么这里我们就开始真正的连接好一个对话请求了,就是利用accept来处理一个个连接的请求
函数原型:int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);参数sockfd就是上面解释中的监听套接字,这个套接字用来监听一个端口,当有一个客户端与服务器连接时,它使用这个一个端口号,而此时这个端口号正与这个套接字关联。当然客户端不知道套接字这些细节,它只知道一个地址和一个端口号。 参数addr 这是一个结果参数,它用来接受一个返回值,这返回值指定客户端的地址,当然这个地址是通过某个地址结构来描述的,用户应该知道这一个什么样的地址结构。如果对客户端的地址不感兴趣,那么可以把这个值设置为NULL。参数len如同大家所认为的,它也是结果的参数,用来接受上述addr的结构的大小的,它指明addr结构所占有的字节个数。同样的,它也可以被设置为NULL。如果不关心客户端连接过来的具体数据,我们可以这样设置。如果accept成功返回,则服务器与客户已经正确建立连接了,此时服务器通过accept返回的套接字来完成与客户的通信。
当然由于当一个客户端连接过来的时候,服务端需要创建一个新的socket与其进行相应的通信,然而一个socket就是一个文件句柄,所以我们服务端支持的最大并发客户端连接数量就需要受限于服务器支持多少个文件句柄同时打开了。服务器有整个系统最大文件打开数量限制,也有单个进程打开文件数量限制,可以通过查看 cat /proc/sys/fs/file-max 得到系统最大的文件数量限制,这个文件不得随意更改,需要更改其大小,需要在/etc/sysctl.conf文件里面修改fs.file-max= 的值,然后运行命令/sbin/sysctl -p 使这个更改生效。查看单个进程打开文件数量使用命令 ulimit -n 可以查看,一般默认是1024,我们可以通过这个命令将其修改掉。
网络编程涉及问题讲解:
谈到网络编程,最容易想到的是多并发的处理,如何让服务端能处理多并发。一般做法是第一用epoll去处理文件句柄,第二使用多线程去处理连接操作。第三使用哈希。第四 更优化的算法。第五 读写锁。
0 0
原创粉丝点击