
打算专门开一期学习札记系列,主要是重新提炼在学习中遇到的一些重点来反复理解。
第一章:理解网络编程和套接字
1.1 网络编程和套接字
网络编程就是编写程序让两台连网的计算机相互交换数据,和我们平时理解的编程差不多,只不过网络编程目的主要是进行网络数据传输。
套接字(),就是插座的意思。顾名思义,套接字的作用也类似于我们充电时的插座(连接电网来供电),它是用于网络数据传输的一个软件设备。
我们在用语言进行网络编程时会用函数创建一个套接字。大体如下。
#include <sys/socket.h>int socket(int domain, int type, int protocol);//这个函数成功时会返回文件描述符,失败时返回-1。然后我们需要绑定地址信息(地址和端口号)。
#include <sys/socket.h>int bind(int sockfd, struct sockaddr *myaddr, socklen_t addrlen);//成功时返回0,失败时返回-1。接着我们需要一个接听的函数,将套接字转化为可接听(连接)状态。
#include <sys/socket.h>int listen(int sockfd, int backlog);//成功时返回0, 失败时返回-1。最后我们需要能够接受连接请求。
#include <sys/socket.h>int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);//成功时返回文件描述符,失败时返回-1。上述主要是针对服务器端的一些操作,针对客户端,我们只需要一个套接字和连接的函数。
#include <sys/socket.h>int connect(int sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));//成功时返回文件描述符,失败时返回-1。原书中的这里有一个编写的服务器端和客户端源码,可以在本地进行相互的传输数据,我这里就不具体演示了,感兴趣可以去看原书自己动手试一下。我这里主要记述一下一些重要的知识点。
1.2 基于Linux的文件操作
这部分主要是有关于的基本知识,由于对我来说不算新的知识点,所以我会相对简单地说明。
在中,也被认为是文件的一种,所以也可以使用文件操作符来进行操作。系统分配标准输入,输出,错误的文件描述符分别为。创建新文件(或套接字)就会分配新的文件描述符。
文件描述符简单来说就是给要操作的文件标个序号,然后操作时指定这个序号就是对其进行操作了。关于文件操作主要和下面几个函数有关:
#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <unistd.h>
//path:文件名的字符串地址,flag文件打开模式信息int open(const char *path, int flag);//成功时返回文件描述符,失败时返回-1。
//关闭对应的套接字或文件的文件描述符int close(int fd);//成功时返回0,失败时返回-1。
//fd:对应文件的文件描述符,buf:保存传输数据的缓冲地址值,nbytes:传输的字节数ssize_t write(int fd, const void * buf, size_t nbytes);//成功时返回写入的字节数,失败时返回-1。
//fd和buf同上,nbytes:能接收数据的最大字节数ssize_t read(int fd, void * buf, size_t nbytes);//成功时返回接收的字节数(遇到文件结尾就返回0),失败时返回-1。此外文件描述符是从小到大的顺序编号,一般从开始,因为是分配个标准的描述符。
1.3 基于Windows平台的实现
这里关于的实现也有编写的服务器端和客户端源码,这里同样不再展示。主要讲讲和使用套接字的不同之处。
在进行套接字编程时我们需要使用函数,设置程序用到的版本,并初始化相应版本的库。
#include <winsock2.h>
//wVersionRequested:程序员要用的Winsock版本信息,lpWSAData:WSADATA结构体变量的地址int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);//成功时返回0,失败时返回非零的错误代码。这里的版本信息我们使用宏函数构建型版本信息:
MAKEWORD(1, 2); //主版本为1, 副版本为2,返回0x0201。MAKEWORD(2, 2); //主版本为2, 副版本为2,返回0x0202。第二个参数我们就要传入类型的结构体变量的地址。(我们需要先初始化一个类型的变量),参考下面的演示:
int main(int argc, char* argv[]){ WSADATA wsaData; .... if(WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) ErrorHandling("WSAStartup() error!"); .... return 0;}创建完之后我们就可以通过套接字传输数据了,而在套接字传输完之后我们还需要注销该库。
#include <winsock2.h>
int WSACleanup(void);//成功时返回0,失败时返回SOCKET_ERROR。调用完该函数之后,库会返还给操作系统,无法再调用相关函数。
1.4 基于Windows 的套接字相关函数及示例
基于的套接字相关函数如下:
#include <winsock2.h>
SOCKET socket(int af, int type, int protocol);//成功时返回套接字句柄,失败时返回INVALID_SOCKET。
int bind(SOCKET s, const struct sockaddr * name, int namelen);//成功时返回0,失败时返回SOCKET_ERROR。
int listen(SOCKET s, int backlog);//成功时返回0,失败时返回SOCKET_ERROR。
SOCKET accpet(SOCKET s, struct sockaddr * addr, int * addrlen);//成功时返回套接字句柄,失败时返回INVALID_SOCKET。
int connect(SOCKET s, const struct sockaddr * name, int namelen);//成功时返回0,失败时返回SOCKET_ERROR。
int closesocket(SOCKET s);//成功时返回0,失败时返回SOCKET_ERROR。中会区分文件句柄和套接字句柄,这一点不同于文件描述符,所以我们在创建函数需要用来存储套接字句柄。
在中由于套接字也算是文件,所以可以使用文件函数的和来进行数据传输。但严格区分函数和套接字函数,下面介绍数据传输函数:
#include <winsock2.h>
//s:套接字句柄,buf和之前一样,len:要传输的字节数,flags;传输数据时用到的多种选项信息int send(SOCKET s, const char * buf, int len, int flags);//成功时返回传输字节数,失败时返回SOCKET_ERROR。
//len:能接收的最大字节数,其他的变量同上。int recv(SOCKET s, const char * buf, int len, int flags);//成功时返回接收的字节数(收到EOF时为0),失败时返回SOCKET_ERROR。这里还要注意中也是有和函数的,的和函数并不等价于的函数。
第二章:套接字类型与协议设置
2.1 套接字协议及其数据传输特性
协议简单来说就是为了完成数据交换而定好的约定(通信规则)。
这里需要深入理解一下第一章提到的函数。
#include <sys/socket.h>
int socket(int domain, int type, int protocol);//domain: 套接字使用的协议族(Protocol Family)信息。//type: 套接字数据传输类型信息。//protocol: 计算机通信中使用的协议信息。协议族(),也就是套接字通信中使用的一个协议分类。比较常用的是协议族。
| 名 称 | 协 议 族 |
|---|---|
| PF_INET | IPv4互联网协议族 |
| PF_INET6 | IPv6互联网协议族 |
| PF_LOCAL | 本地通信的UNIX协议族 |
| PF_PACKET | 底层套接字的协议族 |
| PF_IPX | IPX Novell协议族 |
一个协议族会有多种数据传输方式,而套接字类型(),会决定套接字的数据传输方式。
这里有两种代表性的数据传输方式:
套接字类型1:面向连接的套接字(SOCK_STREAM)
主要有三个特征:
1.传输过程中数据不会消失。
2.按序传输数据。
3.传输的数据不存在数据边界()。
总结成一句话就是可靠的,按序传递的,基于字节的面向连接的数据传输方式的套接字。
在套接字内部有缓冲()也就是字节数组。通过套接字传输的数据将保存在该数组。所以我们在接收到数据后不一定要立马读取,可以在缓冲区填满了之后再读取。因此在面向连接的套接字中和函数的调用次数并不重要,这也说明了这种套接字不存在数据边界。
套接字类型2:面向消息的套接字(SOCK_DGRAM)
有四个主要特征:
1.强调快速传输而非传输顺序。
2.传输的数据可能丢失也可能损毁。
3.传输的数据有数据边界。
4.限制每次传输数据的大小。
同样总结成一句话:不可靠的,不按序传递的,以数据高速运输为目的的套接字。
数据边界:每次传输次数和接收次数需要相同,这种特性就是“传输的数据具有边界”。
面向消息的套接字比面向连接的套接字具有更快的传输速度,但是无法保证数据损毁或丢失,且不存在连接这个概念。
协议的最终选择
第三个参数一般情况下无需考虑直接填即可。除非存在“同一个协议族中存在多个数据传输方式相同的协议”,数据传输方式相同,但是协议不同。此时就算需要通过第三个参数具体指定协议信息。
IPv4协议族中面向连接的套接字:
int tcp_socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);满足前两个参数的指定的协议只有协议,所以这种套接字也叫做TCP套接字。
IPv4协议族中面向消息的套接字:
int udp_socket = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);同样满足前两参数的指定的协议只有协议,所以这种套接字也叫UDP套接字。
这里原书中同样给出了套接字使用的示例,跟第一章的代码差不多,只不过多演示了一下和调用次数不同仍然能完整传递数据的特性。
2.2 Windows平台下的实现及验证
Windows操作系统下的函数:
#include <winsock2.h>
SOCKET socket(int af, int type, int protocol);//成功时返回socket句柄,失败时返回INVALID_SOCKET。这里原作者提到了一些扩展性问题,尽管操作系统下数据类型返回的也是整型,我们还是尽量使用而非接收返回值。同理于。
下的套接字使用示例这里同样也不再赘述。
