目标是一篇搞懂socket编程的所有api,一篇进行实战演练。
首先解决下面几个问题:
1.对socket编程的理解:
也即是:调用一些操作系统提供的api,来与应用进程进行交互。
1.微软公司在其操作系统中采用了套接字接口 API ,形成了一个稍有不同的 API,并称之为 Windows Socket Interface,WINSOCK。
2.Berkeley UNIX 操作系统定义了一种 API,称为 套接字接口(socket interface),简称套接字( socket)。
3.AT&T 为其 UNIX 系统 V 定义了一种 API,简写 为 TLI (Transport Layer Interface)。
这里使用的是WINSOCK
如何标识对外的通信端点呢? IP地址+端口号
操作系统/进程如何管理套接字(对内)?套接字描述符(socket descriptor)是一个小整数 。
类似于文件的抽象
当应用进程创建套接字时,操作系统分配一个数据结构存储该套接字相关信息
返回套接字描述符
使用TCP/IP协议簇的网络应用程序声明端点地址变量时,使用结构 sockaddr_in.
定义结构sockaddr_in:
/*
* Socket address, internet style.
*/
struct sockaddr_in {
short sin_family;/*地址族(TCP/IP:AF_INET) */
u_short sin_port;/*端口号 */
struct in_addr sin_addr;/*IP地址 */
char sin_zero[8];/*未用(置0) */
};
WSAStartup
使用Socket的应用程序在使用Socket之前必须首先调用 WSAStartup函数
两个参数:
第一个参数指明程序请求使用的WinSock版本,其中高位字节指明副版本、低位字节指明主版本.
十六进制整数,例如0x102表示2.1版
第二个参数返回实际的WinSock的版本信息
• 指向WSADATA结构的指针
示例代码如下:使用2.1版本的WinSock的程序代码段 :
wVersionRequested = MAKEWORD( 2, 1 ); //创建字,高字节是2,低字节是1,表示版本2.1
err = WSAStartup( wVersionRequested, &wsaData ); //返回实际的WinSock的版本信息
WSACleanup
应用程序在完成对请求的Socket库的使用, 最后要调用WSACleanup函数解除与Socket库的绑定并释放Socket库所占用的系统资源
不清理的话,在运行codeblocks的时候,常常可能会发生id.exe被占用错误。无法运行,需要去管理器找进程关掉才可以继续运行。
此类型错误处理办法详见我其他博文。
socket
创建套接字,操作系统返回套接字描述符(sd)
第一个参数(协议族): protofamily = PF_INET(TCP/IP)
第二个参数(套接字类型):
type = SOCK_STREAM,SOCK_DGRAM or SOCK_RAW(TCP/IP)
第三个参数(协议号):0为默认
例:创建一个流套接字的代码段
struct protoent *p;
p=getprotobyname("tcp");
SOCKET sd=socket(PF_INET,SOCK_STREAM,p->p_proto);
TCP:可靠、面向连接、字节流传输、点对点
UDP:不可靠、无连接、数据报传输
Closesocket
注意c语言中,如果你不管的话,就会一直保留着资源。。
关闭一个描述符为sd的套接字
如果多个进程共享一个套接字,调用closesocket 将套接字引用计数减1,减至0才关闭
一个进程中的多线程对一个套接字的使用无计数
如果进程中的一个线程调用closesocket将一个套接字 关闭,该进程中的其他线程也将不能访问该套接字
返回值:
0:成功
SOCKET_ERROR:失败
bind
绑定套接字的本地端点地址
IP地址+端口号
参数:
套接字描述符(sd)
端点地址(localaddr) • 结构sockaddr_in
客户程序一般不必调用bind函数
服务器端?
熟知端口号:
IP地址? 地址通配符:INADDR_ANY
参考网站:
有连接的socket客户端通过调用Connect函数在socket数据结构中保存本地和远端信息,无须调用bind(),因为这种情况下只需知道目的机器的IP地址,而客户通过哪个端口与服务器建立连接并不需要关心,socket执行体为你的程序自动选择一个未被占用的端口,并通知你的程序数据什么时候打开端口。
1.需要在建连前就知道端口的话,需要 bind
2.需要通过指定的端口来通讯的话,需要 bind
本来用的是TCP,客户端就不用绑定端口了,绑定之后只能运行一个client的程序属于自己人为设定的障碍,而从服务器那边得到的客户机连接端口号(是系统自动分配的)与这边客户机绑定的端口号根本是不相关的,所以客户端绑定也就失去了意义。
如果bind了,就绑定了服务器的端口,也就导致了绑定之后只能运行一个client的程序。
listen
置服务器端的流套接字处于监听状态
仅服务器端调用
仅用于面向连接的流套接字
设置连接请求队列大小(queuesize)
返回值:
0:成功
SOCKET_ERROR:失败
connect
客户程序调用connect函数来使客户套接字(sd)与特定计算机 的特定端口(saddr)的套接字(服务)进行连接
仅用于客户端
可用于TCP客户端也可以用于 UDP客户端
TCP客户端:建立TCP连接
UDP客户端:指定服务器端点地址
accept
服务程序调用accept函数从 处于监听状态的流套接字sd 的客户连接请求队列中取出排在最前的一个客户请求, 并且创建一个新的套接字来与客户套接字创建连接通道
仅用于TCP套接字
仅用于服务器
利用新创建的套接字 (newsock)与客户通信
send, sendto
send函数TCP套接字(客户与服务器)或调用了 connect函数的UDP客户端套接字
假如调用了connect了,自然就知道了特定端口套接字了,无需再声明destaddr了。
sendto函数用于UDP服务器端套接字与未调用 connect函数的UDP客户端套接字
假如没有调用connect,自然就不知道特定端口套接字,需要声明destaddr,addrlen!!!
我们这里需要指出一点:
/*我要开始连接了!*/给服务器,然后就开始自顾自地用sendto()或者send()发自己地请求。connect()中,调用TCP返回成功是真的建立了连接,但是UDP并不一定真正建立了连接!!!!
UDP就像一个任性的人,他说,我要开始做某事了,于是就不论他人是否合作,自己干了起来。
recv, recvfrom
recv函数从TCP连接的另一端接收数据,或者从调用了connect函数的UDP客户端套接字接收服务 器发来的数据
假如调用了connect了,自然就知道了特定端口套接字了,无需再声明destaddr了。
recvfrom函数用于从UDP服务器端套接字与未调 用connect函数的UDP客户端套接字接收对端数据
类似同上。
setsockopt, getsockopt
setsockopt()函数用来设置套接字sd的选项参数
getsockopt()函数用于获取任意类型、任意状态套 接口的选项当前值,并把结果存入optval
小结:
一个容易忽略的问题:进行网络传输机器两端的“大端机器”和“小端机器“的处理:
网络应用的Socket API(TCP)调用基本流程
到这里,主要使用的API就说完了。