Linux Socket 网络编程.docx
- 文档编号:30207966
- 上传时间:2023-08-07
- 格式:DOCX
- 页数:36
- 大小:400.36KB
Linux Socket 网络编程.docx
《Linux Socket 网络编程.docx》由会员分享,可在线阅读,更多相关《Linux Socket 网络编程.docx(36页珍藏版)》请在冰豆网上搜索。
LinuxSocket网络编程
Linux下的网络编程指的是socket套接字编程,入门比较简单。
在学校里学过一些皮毛,平时就是自学玩,没有见识过真正的socket编程大程序,比较遗憾。
总感觉每次看的时候都有收获,但是每次看完了之后,过段时间不看,重新拾起这些知识的时候又要从头开始,所以,在这里做个笔记也算是做个模板,以后可以直接从某一个阶段开始接着玩。
。
。
1.socket套接字介绍
socket机制其实就是包括socket,bind,listen,connect,accept等函数的方法,其通过指定的函数实现不同的协议(IP4,IP6等)的数据在不同层之间的传输和获取等处理。
其实个人理解socket就是处于应用层和TCP/IP协议之间的一个中间层,具体的数据分析,重组,拆分等操作对于应用层的网络编程者来说都是不可见的,这些都有协议栈内核实现,应用层的网络编程会通过设置socket机制中创建socket时参数不同,而接收或者发送不同类型的数据。
对于TCP/IP在这里就不过多的讲,但是需要提及的是经典的TCP/IP参考模型是分为4个层次:
应用层,传输层,网络互联层,主机到网络层。
标准的套接字编程主要是指TCP和UDP的网络编程,socket网络编程的模式就是分server和client,通过server端首先建立,client端联接进行通信。
网络协议栈内核实现的功能主要就是在数据到达每一层时,给数据加上或者去掉协议包头,或者进行校验,数据重组,拆分等操作,最后得到我们想要的数据格式。
下面简单列一下TCP/IP参考模型中主要的协议类型(图片来自Linux网络编程)。
图1TCP/IP参考模型的层次结构
标准套接字分为TCP和UDP协议两种不同type的工作流程,TCP网络编程相对于UDP来说相对复杂,因为TCP是面向连接的服务,其中包括三次握手建立连接的过程,而UDP则是无连接的服务,下图介绍了TCP服务使用socket套接字建立连接的过程,以及进行数据交互的过程。
图片来自
图2TCP建立socket通信的流程
TCP和UDP的网络编程模式有两种,一种是服务器模式,另一种是客户端模式,因为TCP是面向连接的服务,所以在socket机制当中,TCP的服务器模式比UDP的服务器模式多了listen,accept函数,TCP客户端比UDP客户端多了connect函数。
下面是TCP和UDP网络编程的两种模式流程图。
下面将结合图2,3,4介绍一下TCPsocket的机制是如何实现的。
图3TCP服务器端与客户端通信流程
图4UDP服务器端和客户端通信流程
2.socket套接字基本函数介绍
2.1创建socket套接字
intsocket(intfamily,inttype,intprotocol);
功能介绍:
在Linux操作系统中,一切皆文件,这个大家都知道,个人理解创建socket的过程其实就是一个获得文件描述符的过程,当然这个过程会是比较复杂的。
可以从内核中找到创建socket的代码,并且socket的创建和其他的listen,bind等操作分离开来。
socket函数完成正确的操作是返回值大于0的文件描述符,当返回小于0的值时,操作错误。
同样是返回一个文件描述符,但是会因为三个参数组合不同,对于数据具体的工作流程不同,对于应用层编程来说,这些也是不可见的。
参数说明:
从socket创建的函数可以看出,socket有三个参数,family代表一个协议族,比较熟知的就是AF_INET,PF_PACKET等;第二个参数是协议类型,常见类型是SOCK_STREAM,SOCK_DGRAM,SOCK_RAW,SOCK_PACKET等;第三个参数是具体的协议,对于标准套接字来说,其值是0,对于原始套接字来说就是具体的协议值。
2.2地址端口绑定函数bind
intbind(intsockfd,conststructsockaddr*myaddr,socklen_taddrlen);
功能介绍:
bind函数主要应用于服务器模式一端,其主要的功能是将addrlen长度structsockaddr类型的myaddr地址与sockfd文件描述符绑定到一起,在sockaddr中主要包含服务器端的协议族类型,网络地址和端口号等。
在客户端模式中不需要使用bind函数。
当bind函数返回0时,为正确绑定,返回-1,则为绑定失败。
参数说明:
bind函数的第一个参数sockfd是在创建socket套接字时返回的文件描述符。
bind函数的第二个参数是structsockaddr类型的数据结构,由于structsockaddr数据结构类型不方便设置,所以通常会通过对tructsockaddr_in进行地质结构设置,然后进行强制类型转换成structsockaddr类型的数据,下面是两种类型数据结构的定义和对应关系图。
typedefunsignedshortsa_family_t;
structin_addr{
__be32s_addr;
};
structsockaddr{
sa_family_tsa_family;/*addressfamily,AF_xxx*/
charsa_data[14];/*14bytesofprotocoladdress*/
};
/*StructuredescribinganInternet(IP)socketaddress.*/
#define__SOCK_SIZE__16/*sizeof(structsockaddr)*/
structsockaddr_in{
sa_family_tsin_family;/*Addressfamily*/
__be16sin_port;/*Portnumber*/
structin_addrsin_addr;/*Internetaddress*/
/*Padtosizeof`structsockaddr'. */
unsignedchar__pad[__SOCK_SIZE__-sizeof(shortint)-
sizeof(unsignedshortint)-sizeof(structin_addr)];
};
图5structsockaddr_in和structsockaddr的映射关系
bind函数的第三个参数是指定structsockaddr类型数据的长度,因为前面讲过bind函数的第二个参数是通过设置一个较容易的数据结构,然后通过强制类型转换成structsockaddr,实际上,第二个参数具体的数据结构的长度会根据socket创建时,设置的family协议族的不同而不同,像AF_UNIX协议族的bind函数第二个参数的数据结构应该是structsockaddr_un,其大小和structsockaddr_in不同。
2.3监听本地端口listen
intlisten(intsockfd,intbacklog);
功能介绍:
刚开始理解listen函数会有一个误区,就是认为其操作是在等在一个新的connect的到来,其实不是这样的,真正等待connect的是accept操作,listen的操作就是当有较多的client发起connect时,server端不能及时的处理已经建立的连接,这时就会将connect连接放在等待队列中缓存起来。
这个等待队列的长度有listen中的backlog参数来设定。
listen和accept函数是服务器模式特有的函数,客户端不需要这个函数。
当listen运行成功时,返回0;运行失败时,返回值位-1.
参数说明:
sockfd是前面socket创建的文件描述符;backlog是指server端可以缓存连接的最大个数,也就是等待队列的长度。
2.4接受网络请求函数accept
intaccept(intsockfd,structsockaddr*client_addr,socklen_t*len);
功能介绍:
接受函数accept其实并不是真正的接受,而是客户端向服务器端监听端口发起的连接。
对于TCP来说,accept从阻塞状态返回的时候,已经完成了三次握手的操作。
Accept其实是取了一个已经处于connected状态的连接,然后把对方的协议族,网络地址以及端口都存在了client_addr中,返回一个用于操作的新的文件描述符,该文件描述符表示客户端与服务器端的连接,通过对该文件描述符操作,可以向client端发送和接收数据。
同时之前socket创建的sockfd,则继续监听有没有新的连接到达本地端口。
返回大于0的文件描述符则表示accept成功,否则失败。
参数说明:
sockfd是socket创建的文件描述符;client_addr是本地服务器端的一个structsockaddr类型的变量,用于存放新连接的协议族,网络地址以及端口号等;第三个参数len是第二个参数所指内容的长度,对于TCP来说其值可以用sizeof(structsockaddr_in)来计算大小,说要说明的是accept的第三个参数要是指针的形式,因为这个值是要传给协议栈使用的。
2.5连接目标服务器函数connect
intconnect(intsock_fd,structsockaddr*serv_addr,intaddrlen);
功能介绍:
连接函数connect是属于client端的操作函数,其目的是向服务器端发送连接请求,这也是从客户端发起TCP三次握手请求的开始,服务器端的协议族,网络地址以及端口都会填充到connect函数的serv_addr地址当中。
当connect返回0时说明已经connect成功,返回值是-1时,表示connect失败。
参数说明:
connect的第一个参数是socket创建的文件描述符;第二个参数是一个structsockaddr类型的指针,这个参数中设置的是要连接的目标服务器的协议族,网络地址以及端口号;第三个参数表示第二个参数内容的大小,与accept不同,这个值不是一个指针。
在服务器端和客户端建立连接之后是进行数据间的发送和接收,主要使用的接收函数是recv和read,发送函数是send和write。
因为对于socket套接字来说,最终实际操作的是文件描述符,所以可以使用对文件进行操作的接收和发送函数对socket套接字进行操作。
对于UDP编程来说,其服务器端和客户端之间没有三次握手建立连接,所以服务器端没有listen和accept函数,客户端没有connect函数。
所以对于服务器端来说,没有accept函数,所以使用recvfrom函数来获取数据的同时获得客户端的协议族,网络地址以及端口号;对于客户端来说,没有connect函数,所以使用sendto函数发送数据的同时设置服务器端的协议族,网络地址以及端口;同理如果recvfrom用在客户端,则是接收服务器端数据和地址,sendto用在服务器端,则是发送到客户端网络地址以及端口数据。
2.6接收数据函数recvfrom
ssize_trecvfrom(intsockfd,void*buf,size_tlen,intflags,structsockaddr*from,socklen_t*fromlen);
功能介绍:
对于该函数主要的功能是,从客户端或者服务器端接收数据以及发送方的地址信息存储到本地的structsockaddr类型参数变量当中,如果函数返回-1,所说明接收数据失败,如果返回的是大于等于0的值,则说明函数接收到的数据的大小。
因为可以设置文件描述符的状态为阻塞模式,所以在没有接收到数据时,recvfrom会一直处于阻塞状态,直到有数据接收到。
参数说明:
sockfd是创建socket时的文件描述符;buf用于存储接收到的数据缓冲区,接收的数据将放到这个指针所指向的内容的空间中;len是接收缓冲区的大小;from是指向structsockaddr的指针,接收发送方地址信息;fromlen是表示第5个参数所指向内容的长度,可以使用sizeof(structsockaddr)来定义大小,不过因为是要传给内核协议栈,所以使用了指针类型。
2.7发送数据函数sendto
sizeof_tsendto(intsockfd,constvoid*buf,size_tlen,intflag,conststructsockaddr*to,socklen_ttolen);
功能介绍:
sendto函数主要根据填充的接收方的地址信息向客户端或者服务器端发送数据,接收方的地址信息会提前设置在structsockaddr类型的参数指针中,当返回值-1时,表明发送失败,当返回值大于等于0时,表示发送成功,并且发送数据的大小会通过返回值传递回来。
参数说明:
sockfd是有socket创建的文件描述符;buf是发送数据缓冲区,要发送的数据会放在这个指针指向的内容空间中;len是发送缓冲区的大小;to是一个structsockaddr类型的指针,其指向地址的内容是接收方地址信息;tolen表示第5个参数指向的数据内容的长度,传递的是值,可以用sizeof(structsockaddr)计算。
3.linux线程介绍
通过socket机制建立起的连接,仅仅实现的是服务器端和客户端之间的通信,数据的传输。
但是要使网络编程实现性能更优的话,少不了使用线程,线程间通信以及IO函数,接下来就简单讲一下线程,线程间通信,以及IO函数中的select函数。
Linux下的线程,线程是进程中的一个运行单元,进程fork子进程的过程是对父进程进程copy的过程,然后紧紧改变子进程本身的一些变量,之后各自的进程运行属于自己进程空间的内容;而线程的创建则不然,线程创建在进程中有自己固定的创建函数,在同一个进程中创建的所有线程会共用所在进程的全局变量,信号句柄,文件描述符和当前的目录状态,但是每个线程又会有属于自己的线程栈等私有的属性。
进程获得的使用资源被分给了每个线程,除公共部分外每个线程之间的运行又是相对独立的。
Linux下线程的基本函数:
3.1线程创建函数pthread_create
intpthread_create(pthread_t*thread,pthread_attr_t*attr,void*(*start_routine)(void*),void*arg);
功能介绍:
该函数是用于在进程中创建线程,线程在进程中创建有固定的形式。
个人理解,线程的创建就是圈起了一段代码作为一个线程,这段被圈起来的函数作为线程函数,线程开始运行就是从线程函数开始运行,线程函数也有固定的格式,因为格式固定,线程的创建把单独作为参数的线程函数和线程函数参数整合到一起,形成一个线程。
当然在创建的同时,会设置当前线程的属性,以及用于操作的线程标识符。
参数说明:
thread:
第一个参数是一个pthread_t类型的线程标识符,可以通过操作该标识符,实现对线程的操作;
attr:
第二个参数是用来设置线程的属性,包括线程优先级等属性;
start_routine:
第三个参数是指当线程成功创建后,开始运行的一个单元,该单元需要自己编写,一般会使用无限循环来实现;
arg:
第四个参数是第三个参数线程函数运行时传入的参数,为了防止每个线程函数输入参数不同而难以操作,所以线程创建讲两者分开,这样更灵活,便于操作。
3.2线程结束函数pthread_join和pthread_exit
3.2.1线程函数结束pthread_exit
void pthread_exit(void *retval);
功能介绍:
该函数主要的功能是从被圈起来的线程函数中退出,退出过程中会通过函数的参数指针带出一个对象,当等待线程结束函数pthread_join的第二个参数不是NULL时,会传给这个参数做相应的处理。
参数说明:
函数的参数是一个指针,通过该指针可以传递出当前进程结束时的相关信息,这个值会被pthread_join捕捉到。
3.2.2等待线程结束pthread_join
int pthread_join(pthread_t th,void **return_value);
功能介绍:
函数主要功能是等待线程结束,pthread_exit是主动结束线程,该函数是被动等待线程结束。
函数会处于等待状态,如果函数的第二个参数没有设置为NULL,则会捕捉到从exit传递回的信息。
参数说明:
第一个参数是要等待的线程的标识符,有phread_create函数第一个参数指定其值是多少;第二个参数是一个二维指针,用于等待从pthread_exit返回值。
当然,如果不适用pthread_exit结束线程函数的话,线程函数结束,也就是调用函数的线程结束。
当线程函数运行结束时,该函数用于回收线程的资源。
对于讲理论来说,大家往往更喜欢实例,下面是一个线程的小例子。
1#include
2#include
3#include
4
5pthread_tpth[2];
6
7void*print_message(void*argv)
8{
9printf("Thisisinthread%x!
\n",*((pthread_t*)argv));
10
11if(&pth[0]==argv)
12{
13sleep
(1);
14pthread_exit("1threadexit!
");
15}
16else
17{
18sleep(10);
19pthread_exit("2threadexit!
");
20}
21}
22
23intmain(void)
24{
25void*returnValue;
26
27printf("ThisisinmainfunctionBEFOREpthreadcreate!
\n");
28pthread_create(&pth[0],NULL,&print_message,&pth[0]);
29pthread_create(&pth[1],NULL,&print_message,&pth[1]);
30printf("ThisisinmainfunctionAFTERpthreadcreate%x!
\n",pth[0]);
31pthread_join(pth[0],&returnValue);
32printf("ThisisinmainfunctionAFTERpthreadjoin1\n");
33pthread_join(pth[1],&returnValue);
34printf("ThisisinmainfunctionreturnValue=%s\n",returnValue);
35printf("ThisisinmainfunctionAFTERpthreadjoin\n");
36return0;
37}
下面是运行结果:
这个只是运行结果,其实一些动态的东西,也看不到,因为pthread_join是阻塞等待线程结束的,所以说这个代码是线程1等待一秒首先结束线程运行,pthread_join会捕捉到线程结束,线程2会在线程1结束运行后约9s然后结束运行,这时候线程2的pthread_join才会捕捉到线程结束并释放资源。
所以如果是使用多线程并且在同一个地方统一使用pthread_join释放资源时,最好先释放首先结束运行的线程,然后在释放后结束的线程。
否则的话如果使用pthread_join先释放后结束运行的线程,先结束运行的线程资源因为等待前面pthread_join结束而得不到释放。
因为线程是在进程中创建的,线程公用进程中的资源,所以线程资源的释放非常重要。
主要的线程资源释放的方法有下面三种:
1)在线程函数中调用pthread_detach(pthread_self()),主动释放线程资源;
2)向上面介绍的pthread_join函数,被动释放线程资源;
3)通过设置线程属性中的__detachstate属性,在线程函数运行完,或者pthread_exit退出时,自动释放线程资源,设置线程属性通过下面方式,
pthread_attr_tatt;//线程属性
pthread_attr_init(&att);//初始化线程属性
pthread_attr_setdetachstate(&att,PTHREAD_CREATE_DETACHED);//设置线程属性
pthread_create(pthread_t*pthread,&att,void*(*thread_function)(void*),(void*argv));//建立线程
3.3线程的属性
线程创建函数pthread_create的第二个参数是指线程的属性,当该参数设置为NULL时,表示使用了线程的默认属性。
其实我们可以通过设置第二个参数来设置线程的属性。
线程属性的改变有属于自己的结构体和函数。
线程属性的结构体
typedefstruct__pthread_attr_s
{
int__detachstate;/*线程的终止状态*/
int__schedpolicy;/*调度优先级*/
int__sched_param__shedparam;/*参数*/
int__inheritsched;/*继承*/
int__scope;/*范围*/
int__guardsize;/*保证尺寸*/
int__stackaddr_set/*运行栈*/
void*__stackaddr;/*线程运行栈地址*/
size_t__stacksize;/*线程运行栈大小*/
}pthread_attr_t;
线程主要的属性对象包括上面提到的这几种,线程的属性不可以直接设置,需要通过特定的函数来实现,
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Linux Socket 网络编程 网络 编程