1.网络编程为何物?
我们学习C语言时,一般都是从printf函数和scanf函数进行控制台输入输出开始的。控制台的输入输出和文件的输入输出非常类似。而网络编程,其实也与文件的输入输出十分相像,所以学起来也并不难哦~
按照一般化的定义,网络编程就是编写程序使两台连网的计算机相互交换数据。那么套接字socket又是什么呢?
套接字是网络传输用的软件设备,它就是编程时用来连接网络的工具,就像插槽的插口一样方便,因此得名。关于套接字的本质和功能,我们在本文后话再述。
现在大家知道,网络编程,即是套接字编程。
作为用户,我们关心的是什么样的网络服务呢?
2.套接字服务
我们知道,网络通信是一个复杂的过程,既存在功能的层次性(OSI及TCP/IP层次模型),又存在协议的多样性(不同层有诸多不同的协议),还有应用的多样性(面对不同的用户,不同的应用,甚至于对相同的用户在不同时间或者场合也需要提供不同的应用)。
那么,用户需要的是怎样的服务呢?复杂一点,还是简单一点?
这一点是容易理解的:用户不喜欢复杂(人性化交互是我们科技信息领域的追求),不过也不喜欢太过简单(用户也希望有一定的选择和控制而不是被懂地获取服务)。
3.套接字的本质
从上面的内容中,我们已经了解到,套接字提供给用户需要的服务。
套接字(在位置上)在传输层之上,直接面向用户,(在作用上)提供端到端的通信,还能进行多种服务控制。简单来说可以按照如下来理解:
套接字是一个标识,是一种结构,是一个服务访问点(数据+服务)。
那么套接字是如何来表示的呢?
这就不能不提到套接字变量又称套接字描述符。
SOCKET s;
获得一个套接字类型变量,可通过它得到通信服务,其中的s就是套接字描述符。
SOCKET s1,s2;
代表两个不同的通信--s1,s2在此处起的是通信标识作用。
不过,尤其值得初学者注意的是,SOCKET类型就是整型(所以用 int s 来声明套接字变量其实也并无不可),是一个起标识作用的数值。
4.套接字上的操作
既然网络编程又称套接字编程,那么很容易想到,网络编程以套接字为标识,通信服务都是围绕套接字进行的。
一个简单的通信流程大致都应该包含以下(对于套接字)的操作:
SOCKET s; //申请套接字标识符
s = socket(...); //获得套接字资源
send(s,...); //发送数据
recv(s,...); //接收数据
closesocket(s); //关闭通信进程
ioctlsocket(s,...); //控制套接字工作参数
setsockopt(s,...); //控制套接字工作参数
5.端口与端点
既然套接字是面向用户提供端到端的通信,那么“端到端”此处是指端口间的通信还是端点间的通信呢?让我们来谈一谈端口与端点。
端口(port)是应用层用来区分应用进程的通信标识,这也就意味着,不同的应用进程在同一时间不可使用同一个端口。
端口号有全局分配(固定分配)和本地分配(动态分配)两种分配方式。全局分配通常是将特定(知名)端口号指定给互联网上使用广泛的服务,本地分配则是通常分配给用户编写的进程。
其实这两种分配方式并不矛盾,反而是相辅相成的,可以这么理解:知名端口号提供给我们的是刚需(如吃饭喝水等),而本地动态分配的端口号是我们的个人定制(如玩游戏,看小说等)。
介绍了端口号,接下来再看端点(端点地址结构)。
来思考这样一个问题:为了在网络上唯一确定一个通信应用进程,需要哪些要素?
确定通信应用进程 = 端点 + 协议 = (IP地址 + 端口号 + 协议)
以上是半相关,知道了谁在通信;
确定一次通信 = 本地端点 +协议 + 远端端点 = (本地IP+本地端口+协议+远端端口+远端IP)
以上是全相关,知道了谁在和谁通信。
端点地址结构为何?它又叫套接字地址,结构要素主要是IP和端口号(注意协议类型并不在结构中记录)。
sockaddr_in 结构如下所示:
struct sockaddr_in{
u_short sin_family; // 协议族类型
u_short sin_port; // 端口号
struct in_addr sin_addr; // IP地址
char sin_zero[8]; // 保留,没有使用
}
对于协议族类型而言,若为Internet,则使用AF_INET;
对于IP地址,它作为一个结构体,遵循如下定义:
struct in_addr{
union {
struct { u_char s_b1,s_b2,s_b3,s_b4; } S_un_b;
struct { u_short s_w1,s_w2; } S_un_w;
u_long S_addr;
} S_un;
}
如此定义一个本质为长整形的IP地址,目的何在呢?当然是为了方便使用。
例如,给端点赋值IP地址为 192.168.1.1,有两种方式。用单字节赋值方式:
addr.sin_addr.S_un.s_un_b.s_b1 = 192;
addr.sin_addr.S_un.s_un_b.s_b2 = 168;
addr.sin_addr.S_un.s_un_b.s_b3 = 1;
addr.sin_addr.S_un.s_un_b.s_b4 = 1;
或者是用整体赋值方式:
addr.sin_addr.S_un.s_addr = 0xc0a80101;
注意,还有一种通用套接字地址结构,表示如下:
struct sockaddr{
u_short sa_family;
char sa_data[14];
}
sockaddr没有具体定义端点中的端口及地址的详细内容,但是sockaddr和sockaddr_in两种结构之间可以进行强制类型转换(思考一下,这样有什么用?)。
To Be Continued…