![ebdf1a9a99feebb47ea2031fdd5472c4.png](https://i-blog.csdnimg.cn/blog_migrate/7003e2aab391e75a7c09c8f9b064cd9c.jpeg)
创建socket
linux在 sys/socket.h 下定义了 socket() 系统调用,来创建一个socket,返回一个文件描述符,读写文件的函数也可以用来操作socket。
![f1e9469d9fbfbe23402ddd810a6607d6.png](https://i-blog.csdnimg.cn/blog_migrate/5b5315337b24a2a4e151b336232af319.jpeg)
- domain参数
在之前 Linux socket地址结构体 文章中介绍了三种协议族,domain参数用来指定用哪种协议族,常见的是 PF_INET(IPv4)、PF_INET6(IPv6) 和 PF_UNIX(Unix本地域)。
- type参数
type参数指定服务类型,常见的是SOCK_STREAM(流服务)、SOCK_DGRAM(数据报)、SOCK_RAW(原始套接字)。对于TCP/IP协议族,需要指定type为 SOCK_STREAM,如果想使用UDP协议则用 SOCK_DGRAM。SOCK_RAW 除了可以处理普通的网络报文之外,可以用来处理前面所述无法处理的报文,如ICMP、IGMP等,可以由用户构造IP头,总体来说,SOCK_RAW 可以处理一些特殊协议报文以及操作IP层及其以上的数据。
- protocal参数
protocal参数表示在前面两个参数筛选出的协议集合中,再指定一个具体的协议,传入0表示使用默认协议。通常前两个参数就可以确定一个要使用的协议了,一般这个参数传0就可以。
- 返回
socket()返回一个文件描述符,对socket操作的其他函数,往往需要传入这个值来指定要操作的是哪个socket。socket()调用失败返回-1,并设置errno。用户可以 #include ,然后使用errno变量。
命名socket
通常,网络编程中服务器端程序要绑定一个socket地址,如tcp上的80端口,来提供给客户端连接。给socket绑定地址叫做命名socket,不命名socket表示采用操作系统自动分配的地址。服务器端通常需要命名socket,客户端一般不需要。
linux提供的命名socket的系统调用为:
![e70b670208a8d11be1ec1b31d9813a70.png](https://i-blog.csdnimg.cn/blog_migrate/63e84e8430a79f1d774064c32c5f0600.jpeg)
- sockfd参数
前面所述socket()返回的文件描述符。
- addr参数
指定要绑定的socket地址,sockaddr类型的指针,可以由sockaddr_in、sockaddr_in6、sockaddr_un等类型强转过来,详见 Linux socket地址结构体。
- addrlen参数
上述addr参数所指结构的大小。
- 返回
成功时返回0,失败返回-1并设置errno。bind()常见的errno值为 EACCES 和 EADDRINUSE。EACCES表示当前用户没有权限绑定的地址,如端口0~1023。EADDRINUSE表示当前地址已经被占用。
监听socket
命名socket之后,还不能马上接收连接,需要创建一个队列,以便客户端连接较多时,存储待处理的连接。如果队列被占满,后续再在客户端连接,会收到 ECONNREFUSED 错误,即连接被拒绝。
linux提供了listen系统调来创建这个队列:
![c42359b0d789cedec6e597a7080d39a5.png](https://i-blog.csdnimg.cn/blog_migrate/9bccf963d0e530df566f2b959d46cef7.jpeg)
- sockfd参数
前面所述socket()返回的文件描述符。
- backlog参数
指定队列的大小。
- 返回
成功时返回0,失败返回-1并设置errno。
接受连接
linux提供了accept系统调用来接受连接:
![e398d877a19146f4ae8059afbd9cfd0c.png](https://i-blog.csdnimg.cn/blog_migrate/fc8403f34302a4845942be4316eb365b.jpeg)
- sockfd参数
前面所述socket()返回的文件描述符。
- addr参数
接受客户端连接的socket地址。
- addrlen参数
上述addr参数所指结构的大小。
- 返回
失败返回-1并设置errno。成功时,返回一个新的文件描述符,对应接受的客户端连接,后续与客户端通信时读写此文件描述符即可。
读写数据
文件读写的函数同样可以用来读写socket,但是系统还提供了专门用于socket数据读写的函数:
![34841a13ec6e216015995fa55626b278.png](https://i-blog.csdnimg.cn/blog_migrate/36b726fdeeb9e4c6a41f0d49fde8640b.jpeg)
发起连接
connect函数用于客户端发起连接。
![32eae2c784608d739edf276dae0a575a.png](https://i-blog.csdnimg.cn/blog_migrate/a67d2b9d09e58c884e9d5ef4edc91814.jpeg)
成功返回0,失败返回-1并设置errno。常见的errno为 ECONNREFUSED 和 ETIMEDOUT,分别表示服务端拒绝连接、连接超时。
关闭连接
关闭文件的函数可以用来关闭socket连接,如close()函数:
![6de4f4ff07f45a02cd639ef6e64598fb.png](https://i-blog.csdnimg.cn/blog_migrate/71f5514c283739015031f296a76810db.jpeg)
当在多进程编程中,一个进程fork之后得到的父子进程都可以操作原来的socket,这种情况下fork时会把原来父进程中打开的socket的引用记数加1,无论是在父进程还是子进程中,调用close()时只是把socket的引用记数减1,减到0时才会真正地关闭连接。如果想要立即关闭连接,而不是减引用记数的话,可以用专门针对网络编程的shutdown()函数:
![7716c3e435581b55fd2a2aa3b7616934.png](https://i-blog.csdnimg.cn/blog_migrate/7516f9d978d17bd4726466b95328e5d8.jpeg)
howto参数提供了三种关闭方式:
- SHUT_RD:关闭sockfd上的读,应用程序不能再执行读操作,并且socket接收缓冲区中的数据都被丢弃。
- SHUT_WR:关闭写,应用程序不能再执行写操作,但是发送缓冲区中的数据会在真正关闭连接之前全部发送出去。
- SHUT_RDWR:同时关闭读写。
shutdown()成功时返回0,失败返回-1并设置errno。
服务器端示例
![17d7b89ee17d194c54ca78e1f8dbc054.png](https://i-blog.csdnimg.cn/blog_migrate/8edbcacb62a3a6afb5bf28407e5d4e1d.jpeg)
![be6bd835bea409d01c01b376fee0a050.png](https://i-blog.csdnimg.cn/blog_migrate/db4c7bdb193df24b2f43f13773840f25.jpeg)
客户端端示例
![f61c4367def1324c52eb724bc45267c6.png](https://i-blog.csdnimg.cn/blog_migrate/d9c1eb3a256940882ad99bdb298ddffa.jpeg)