socket到底是个什么东西,socket是个TCP协议吗?我们平时很多方面都会用到socket,但确定真的了解socket吗?
一.说起Socket我们在说什么?
Wikipedia:A network socket is an endpoint of a connection in a computer network. In Internet Protocol (IP) networks, these are often called Internet sockets. It is a handle (abstract reference) that a program can pass to the networking application programming interface (API) to use the connection for receiving and sending data. Sockets are often represented internally as integers.
个人理解:socket其实就是一根通信电缆两端的电话终端,电话接通后就相当两个socket建立了连接,两个电话之间可以相互通话,两个socket之间就可以实时收发数据,socket仅仅是一个通信工具,通信工具,通信工具重要的事说三遍(OSI模型中的第四层传输层的API接口,这一层通常使用两种协议TCP或UDP来传输)并不是一种协议。TCP、UDP、HTTP才是我们通常理解的协议。
也就是说,Socket这个工具一般使用TCP和UDP两种协议来通信,否则光杆socket并没有毛用。其实我们所认识到的互联网中的各种通信:web请求、即时通讯、文件传输和共享等等底层都是通过Socket工具来实现的,所以说互联网一切皆socket。搞懂了socket你就相当于打通了任督二脉。
二.Socket的8个必备函数
socket并不可怕,我们只需掌握下面几个C语言socket函数个人觉得就够用了。
1. int socket(int domain, int type, int protocol);
socket函数对应于普通文件的打开操作。普通文件的打开操作返回一个文件描述字,而socket()用于创建一个socket描述符(socket descriptor),它唯一标识一个socket。这个socket描述字跟文件描述字一样,后续的操作都有用到它,把它作为参数,通过它来进行一些读写操作。
正如可以给fopen的传入不同参数值,以打开不同的文件。创建socket的时候,也可以指定不同的参数创建不同的socket描述符。
socket函数的三个参数和return分别为:
domain:即协议域,又称为协议族(family)。通常我们只需关心这两个协议族就够了AF_INET、AF_INET6。AF_INET表示创建IPv4的socket,那么AF_INET6就表示创建IPv6的socket。
type:指定socket类型。常用的socket类型有,通常我们只需关心SOCK_STREAM、SOCK_DGRAM这两个类型也就够了,SOCK_STREAM表示TCP类型的socket,SOCK_DGRAM表示UDP类型的socket。
protocol:故名思意,就是指定协议。常用的协议有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议。通常使用中只需记住这个参数设为0就够了。当protocol为0时,会自动选择type类型对应的默认协议。
return:套接口描述字。如果出现错误,它返回-1,并设置errno为相应的值
当我们调用socket创建一个socket时,返回的socket描述字它存在于协议族(address family,AF_XXX)空间中,但没有一个具体的地址。如果想要给它赋值一个地址,就必须调用bind()函数,否则就当调用connect()、listen()时系统会自动随机分配一个端口。具体下文详细说明。
2. int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
正如上面所说bind()函数把一个地址族中的特定地址(IP+Port)赋给socket。例如对应AF_INET、AF_INET6就是把一个ipv4或ipv6地址和端口号组合赋给socket。作为服务端我们必须给它指定一个端口号,要不然客户端就不知道该连哪个端口了。所以服务器一般初始化问socket必须调用bind()函数绑定地址然后才能listen()。而客户端一般初始化完socket并且知道服务器IP地址和端口号就直接可以调用connect()函数进行连接了,不需要绑定自己的地址,因为系统随机给客户端分配的地址(IP+Port)已经默默发送到服务器了。
好了说了这么多,该说说三个参数及return的含义了:
sockfd:即socket描述字,它是通过socket()函数创建了,唯一标识一个socket。bind()函数就是将给这个描述字绑定一个名字。
addr:一个const struct sockaddr *指针,指向要绑定给sockfd的协议地址。这个地址结构根据地址创建socket时的地址协议族的不同而不同,如ipv4对应的是:
struct sockaddr_in {
sa_family_t sin_family; /* address family: AF_INET */
in_port_t sin_port; /* port in network byte order */
struct in_addr sin_addr; /* internet address */
};
addrlen:对应的是地址的长度。
return:成功返回0,失败返回-1
3. int listen(int sockfd, int backlog);
作为一个服务器,在调用socket()、bind()之后就会调用listen()来监听这个socket,如果客户端这时调用connect()发出连接请求,服务器端就会接收到这个请求。socket()函数创建的socket默认是一个主动类型的,listen函数将socket变为被动类型的,等待客户的连接请求。
sockfd:即为要监听的socket描述字
backlog:相应socket可以排队的最大连接个数。
return:成功返回0,失败返回-1
4. int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
作为客户端初始化完socket,不需要bind()就直接可以connect()与服务端建立连接了。因为系统会自动生成一个随机的地址(具体应该为本机IP+随机端口号)。
sockfd:还没绑定客户端具体地址的socket描述字
addr:即将要连接到服务端的地址(IP+port)
addrlen:地址长度
return:如果是阻塞连接,成功立即返回0,如果失败,在iOS系统上超时大约一分钟后返回-1
5. int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
TCP服务器端依次调用socket()、bind()、listen()之后,就会监听指定的socket地址了。TCP客户端依次调用socket()、connect()之后就向TCP服务器发送了一个连接请求。TCP服务器监听到这个请求之后,就会调用accept()函数取接收请求,并把会这样连接就建立好了。之后就可以开始网络I/O操作了,即类同于普通文件的读写I/O操作。
sockfd:已经绑定具体服务端地址的socket描述字。
sockaddr:一般为NULL,这个参数可以理解为interface指定连接必须从哪里来的,比如是localhost、wifi还是以太网卡。NULL为任意方式。
addrlen:sockaddr地址长度
return:成功返回服务器端socket描述字否则错误。
6. ssize_t write(int fd, const void *buf, size_t count);
服务端与客户端建立了通信接下来就可以实现网络通信了,可以调用网络I/O进行读写操作了,即实现了网络中不同进程之间的通信!
fd:要写入的的socket文件描述符
buf:将要被写入的缓冲区数据
count:被写入的数据长度
return:返回值大于0,表示写了部分数据或者是全部的数据,这样用一个while循环不断的写入数据,但是循环过程中的buf参数和count参数是我们自己来更新的,也就是说,网络编程中写函数是不负责将全部数据写完之后再返回的,说不定中途就返回了!返回值小于0表示出错。代码见第三部分
7. ssize_t read(int fd, void *buf, size_t count);
如果系统事件源有了一个读取信号事件发生,那么我们可以调用read方法读取网络I/O中的数据。
fd:建立连接的socket文件描述符
buf:读取数据后放入的缓冲区
count:缓冲区大小
return:当读取成功时,read返回实际读取到的字节数,这样我们可以用一个while循环不断的读取数据,但是循环过程中的buf参数和count参数是我们自己来更新的,也就是说,网络编程中写函数是不负责将全部数据读取完之后再返回的。如果返回值是0,表示已经读取到文件的结束了,小于0表示是读取错误。代码见第三部分
8. int close(int fd);
在服务器与客户端建立连接之后,会进行一些读写操作,完成了读写操作就要关闭相应的socket描述字,好比操作完打开的文件要调用fclose关闭打开的文件。
三.循环读取和写入代码
循环写入代码
循环读取代码