马志强 软件学院501室 zqma@hit.edu.cn 网络应用开发 马志强 软件学院501室 zqma@hit.edu.cn
TCP/IP协议模型 TCP UDP IP 应用层 应用层 表示层 FTP DNS 其它 HTTP POP SMTP 网络接口协议 (链路控制和媒体访问) 以太网 令牌环 X.25 FDDI TCP UDP IP TELNET ICMP / IGMP ARP / RARP 会话层 传输层 传输层 网络层 互连网层 放的协议标准 独立于特定的计算机硬件与操作系统 独立于特定的网络硬件,可以运行在局域网、广域网,更适用于互联网络中; 统一的网络地址分配方案,使得整个TCP/IP设备在网中都具有惟一的地址; 标准化的应用层协议,可以提供多种可靠的网络服务 数据链路层 网络接口层 (物理网络) 物理层
本节内容 2. 客户-服务器应用模型与套接字 客服-服务器模型与软件设计 客服-服务器软件中的并发处理 套接字编程架构 套接字API
网络应用开发 网络应用 很少有应用程序运行在网络核心的设备上 运行在不同的端系统 通过网络进行交互 application transport network data link physical 网络应用 运行在不同的端系统 通过网络进行交互 例如:web server 与 browser 很少有应用程序运行在网络核心的设备上 网络核心设备高度定制不运行用户程序 application transport network data link physical application transport network data link physical
为什么要开发网络应用? 驱动力:不断增加的应用需求 每天都有新的应用或应用层协议被开发 网络关注的重点已经由建网转变为使用 知名的应用并不能满足所有的需要 据统计,超过1/5的网络流量被非知名应用 为适应一些特定的需求(办公/娱乐/开发/…) 技术进步(硬件/软件)带来的软件升级 解决软件的bug/不足,不断升级 每天都有新的应用或应用层协议被开发 网络关注的重点已经由建网转变为使用
网络应用程序的体系结构 网络体系结构 应用程序研发者设计,规定了如何在各种端系统中组织该应用程序 客户服务器(client-server: C/S) 点到点(peer-to-peer: P2P) 混合式(hybrid of C/S and P2P)
客户服务器结构 服务器: 客户端: 通常部署在高性能终端 具有永久固定IP地址 开放的可同处理多个请求 主动访问服务器 间歇性的服务器建立连接 IP地址不固定 客户端间不直接通信 客户端间交互通过服务器 client client client server
点到点结构 高可扩展 难于管理 对等结点: 不一定运行在服务器上 IP地址不固定 结点间间歇互联 结点之间直接通信 例如:Gnutella, BT 高可扩展 难于管理 client client client client
混合式结构 特点: 应用: 客户端之间直接通信 通过服务器协调 Skype 即时通讯 server client QQ,MSN…
两个程序间的通信如何汇合 现实生活中的习惯 客户/服务器通信模式 Why? 一方 在事先约定好的位置被动等待(服务器) 另一方 主动到约定位置联络对方(客户) 客户/服务器通信模式 几乎所有的网络应用程序都使用这种模式 Why? TCP/IP仅提供传输数据的基本机制,没有提供消息到达后启动程序的机制
客户-服务器模式:术语和概念 通信发起方向来区分 客户:主动发起对等通信的应用程序 服务器:被动等待接收客户通信请求的程序 每次执行时都与服务器联系 容易构建,往往不需要系统特权 属于常规的网络应用程序,如浏览器 服务器:被动等待接收客户通信请求的程序 接收客户的请求 执行必要操作 返回结果给客户
客户端开发:可用性 提高客户软件的可用性 可由用户指定服务器的地址、端口、协议等 适用性好,便于测试 界面友好 尽量节省主机资源 简洁,符合用户操作习惯 网络不可靠的情况,能够发现和处理 尽量节省主机资源 不泄露用户隐私
服务器开发:复杂性 服务器通常可以同时响应多个客户请求 服务器经常需要访问受操作系统保护的资源 并发和特权导致服务器软件的复杂性 保证性能:小系统几十个,大系统百万千万 保证稳定:7 x 24小时不间断运行 服务器经常需要访问受操作系统保护的资源 文件、数据库:需要系统特权 不能把特权传递给使用服务的客户 服务器需要处理的安全问题 鉴别:验证客户身份 授权:判断客户是否可以使用自身提供的服务 安全:确保数据不被无意泄露或者损坏 保密:防止未经授权的访问信息 保护:确保网络程序不能滥用系统资源 并发和特权导致服务器软件的复杂性
并发的概念 并发有两种 真正的并发(并行:Parallelism) 两个或者多个事件在同一时刻发生 单处理器下无法实现 表面上的并发(并发:Concurrency) 两个或者多个事件在同一时间段内发生 B C A D CPU CPU CPU
服务器中的并发 并发处理多个传入请求 多个远程用户同时使用多种服务 server client
客户软件的并发 要使客户软件并发执行,一般并不需要程序员为此做大量工作 早期的dos系统,单任务系统,实现并发较难 现代操作系统(windows, linux)等都是多任务系统,对于多个同时运行的客户程序,系统会自动控制并发执行
操作系统的并发 程序 进程:程序的执行态 程序本身没有“运动”的含义,表现为文件,不能作为一个独立单位得到操作系统的认可 程序:一组指令及指令参数的组合(静态) 进程:程序的执行态 用户角度:执行着的程序,应用程序的执行态 操作系统角度:系统资源分配的基本单位 进程号、内存、CPU时间片… 程序本身没有“运动”的含义,表现为文件,不能作为一个独立单位得到操作系统的认可 一个进程只能对应一个程序 一个程序可以对应多个进程 [戏剧] 与 [剧本]
进程并发 多进程操作系统 实现方式:分时多任务 主流操作系统都是多进程的——多任务 操作系统内部有一个进程队列,通过算法控制进程的调度执行 调度算法(FIFO, RR, NORMAL) 考虑优先级、活跃度等因素 每个进程会分配到一小段CPU时间,时间片结束切换到其他进程执行
进程调度的上下文切换 进程切换时,需要切换上下文(Contex) 时间片选择 任务上下文:进程执行的环境 程序计数器、堆栈指针、通用寄存器、页目录等 切换的开销 时间片选择 大:切换开销小,CPU利用率高,并发性差 小:切换开销大,CPU利用率低,并发性好 实际使用时采用多种策略
进程的创建 Unix/Linux系统 Windows系统 Java语言 fork() exec() CreateProcess() Runtime.getRuntime().exec() Desktop.getDesktop().open()
顺序执行的C实例 int main(int argc, char *argv[]) { printf("Hello! "); return 0; } 输出: Hello!
程序的并发版本 int main(int argc, char *argv[]) { fork(); printf("Hello! "); return 0; } 输出: Hello!
进程的分离 int main(int argc, char *argv[]) { int pid; if ((pid = fork()) < 0) { printf("fork err!"); exit(1); } else if (pid == 0) { printf("Hello fork! "); } else { printf("Hello! "); } return 0; 输出: Hello! Hello fork!
执行新程序 int main(int argc, char *argv[]) { int pid; if ((pid = fork()) < 0) { exit(1); } else if (pid == 0) { execl("./hello", "./hello", NULL); } printf("Hello! "); return 0;
fork和exec的区别 对于fork 对于exec 子进程复制父进程的所有进程内存到其内存地址空间中。父、子进程的“数据段”,“堆栈段”和“代码段”完全相同,即子进程中的每一个字节都和父进程一样 子进程的当前工作目录等环境变量和父进程相同,fork之前父进程打开的文件描述符,在子进程中同样打开 子进程拥有自己的进程ID 对于exec 进程调用exec后,将在同一块进程内存里用一个新程序来代替调用exec的那个进程,新程序代替当前进程映像,当前进程的“数据段”,“堆栈段”和“代码段”被新程序改写 新程序会保持调用exec进程的ID不变。
父子进程间同步 父进程先于子进程终止 子进程先于父进程终止 僵尸进程会导致资源浪费,而孤儿则不会 孤儿进程(Orphan process):父进程退出,子进程还在运行 孤儿进程将被init进程(进程号为1)所收养,并由init进程对他们完成状态收集工作 子进程先于父进程终止 系统保留一定量的状态信息,如进程ID、终止状态、CPU时间等 有SIGCHLD signal发送给父进程,父进程可用wait/waitpid处理 一个子进程在其父进程没有调用wait/waitpid的情况下退出,这个子进程就是僵尸进程 僵尸进程会导致资源浪费,而孤儿则不会
获取子进程终止信息 pid_t wait(int *stat_loc) 该函数将挂起当前进程,直到有一个子进程终止或者被信号中断。 当调用该系统调用时,如果有一个子进程已经终止,则该系统调用立即返回,并释放子进程所有资源。 pid_t waitpid(pid_t pid, int *stat_loc, int option); 返回终止子进程的ID-成功;-1-出错;stat_loc存储子进程的返回值; 当pid=-1,option=0时,该函数等同于wait,否则由参数pid和option共同决定函数行为,其中pid参数意义如下: -1:要求知道任何一个子进程的返回状态 >0:要求知道进程号为pid的子进程的状态
waitpid举例 int main(void) { pid_t pid; if ((pid = fork()) < 0) { printf("fork error"); } else if (pid == 0) { // 子进程先执行 printf("Output from child\n"); } else { // 父进程先执行 int stat_val; waitpid(pid, &stat_val, 0); if (WIFEXITED(stat_val)) printf("Child exited with code %d\n", WEXITSTATUS(stat_val)); else if (WIFSIGNALED(stat_val)) printf("Child terminated abnormally, signal %d\n", WTERMSIG(stat_val)); }
多进程服务器的问题 传统的网络服务器程序大都在新的连接到达时,fork一个子进程来处理。虽然这种模式很多年使用得很好,但fork有一些问题: fork是昂贵的:fork时需要复制父进程的所有资源,包括内存映象、描述字等;目前的实现使用了一种写时拷贝(copy-on-write)技术,可有效避免昂贵的复制问题,但fork仍然是昂贵的 fork子进程后,父子进程间、兄弟进程间的通信需要进程间通信IPC机制,给通信带来了困难 系统中进程个数也有限制
线程概念 有时被称为轻量级进程,是程序执行流的最小单元,被主流操作系统支持 一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成 代码 数据 文件 代码 数据 文件 线程资源 线程 线程资源 线程 线程资源 线程
线程特点 系统资源 调度 共享进程资源 进程为资源拥有的基本单位 线程不拥有系统资源(轻型实体) 切换速度快,同进程中线程切换不会引起进程切换,避免昂贵系统调用 两级并发,提高系统吞吐 共享进程资源 与所属进程具有同样的地址空间,可访问进程所拥有的已打开文件、定时器、信号量等
线程的创建 Unix/Linux系统 Windows系统 Java语言 pthread_create() _beginthread () 实现Runnable接口实现它的run()方法 继承Thread类,覆盖它的run()方法
使用线程需注意问题 多个线程访问同一个变量时,需要互斥 Unix/Linux系统 Windows系统 Java语言 pthread_mutex_t, pthread_mutex_lock(), pthread_mutex_unlock() Windows系统 CreateMutex(), WaitForSingleObject(), ReleaseMutex() Java语言 synchronized语句,各种lock 使用互斥信号,注意死锁问题 两个线程争用非共享资源且互不相让 A线程等待B占有的资源,B等待A占有的资源
编写有效使用线程的复杂程序并不十分容易! 线程使用注意事项 编写有效使用线程的复杂程序并不十分容易! 多个线程共享相同的变量,需解决互斥和死锁问题,当程序较复杂时,问题不易发现 同步影响效率,要求设计时尽量避免同步操作,这就对系统的设计提出较高要求 虽然线程是轻量级的,但线程太多同样会造成系统消耗过大,需要把线程的数量和状态限制在可控的范围内 何时创建、是否退出、何时退出 线程池 多个线程协作完成一个任务时,一旦出现问题,不易调试 观察现象,日志文件
异步I/O 某些OS允许单个进程(线程)控制多个输入输出操作 使用select询问操作系统I/O设备的情况 循环查询多个设备I/O 对其中就绪(读/写)的设备进行操作 保证读写非阻塞
本节内容 2. 客户-服务器应用模型与套接字 客服-服务器模型与软件设计 客服-服务器软件中的并发处理 TCP/IP协议的程序接口 套接字API
应用程序如何使用TCP/IP TCP/IP协议软件由操作系统提供 应用程序如何使用TCP/IP通信? 驻留在操作系统中,对用户不可见 运行在系统态,受到内核保护 应用程序运行在用户空间,无法直接访问 应用程序如何使用TCP/IP通信? 与操作系统交互并请求其服务 通过操作系统提供的API(应用程序接口) 即系统调用
系统调用 应用程序和操作系统过程之间传递控制权 被大多数操作系统使用 无论看上去还是使用上都像是函数调用 用户地址空间中的应用程序 应用程序启动系统调用时,控制权从应用程序传递给系统调用接口 此接口又将控制权传递给了操作系统 操作系统将此调用传递给某个内部过程,该过程执行所请求的操作 内部过程一旦完成,控制权通过系统调用接口返回给应用进程,然后应用程序继续执行。 供应用程序调用的系统函数 系统调用接口 包括TCP/IP协议软件的操作系统内核 系统地址中的协议软件
系统调用过程 输入 输出 copy copy 用户空间 输入 输出 内核空间 系统内部过程
松散指定的协议API TCP/IP设计考虑兼容不同的系统 TCP/IP和应用程序之间的接口是松散指定的(loosely specified) 尽量避免使用任何一家厂商的内部数据表示 尽量避免上接口使用仅在某OS中才出现的特征 TCP/IP和应用程序之间的接口是松散指定的(loosely specified) 没有规定应用软件与TCP/IP协议软件接口细节 只提供概念性接口规约:建议需要的功能集 允许系统设计者选择有关API的具体实现细节 TCP/IP windows API TCP/IP linux API
松散指定优缺点 优点 缺点 实际使用的套接字接口 便于各种OS实现TCP/IP 接口可以是过程的,也可以是消息的 不同的OS中的接口细节不同 应用程序移植性受到影响 实际使用的套接字接口 Berkeley UNIX中的套接字接口(包括Linux) Microsoft windows中的WINSOCK AT&T的UNIX系统V的TLI 过程或消息:最适应所用操作系统的方式
TCP/IP建议接口功能 分配用于通信的本地资源 指定本地和远程通信端点 启动连接(客户端) 等待连接(服务器端) 发送或接收数据 判断数据何时到达 产生紧急数据 处理到来的紧急数据 从容终止连接 处理来自远程端点的连接终止 异常终止通信 处理错误条件或者连接异常终止 通信结束后释放本地资源 尽管TCP/IP没有规定一种应用程序接口,但对接口所需要的功能提出了建议 概念性操作 定义为一组过程和函数,同时提出了每个过程或函数要求的参数,以及他们执行操作的语义
系统实现网络通讯的基本方法 使用新的系统调用来访问TCP/IP 使用一般的I/O调用来访问TCP/IP 混合方法 对每一个概念性操作实现一个系统调用 除非绝对必要,创建新的系统调用并不明智 使用一般的I/O调用来访问TCP/IP 使用一般的I/O调用,但进行扩充 既可以用于I/O,又可以用于网络协议 混合方法 尽可能使用基本I/O功能 增加一些额外函数来实现其它操作
LINUX中的基本I/O操作 操作 含义 open close read write lseek ioctl 为输入或输出操作准备一个设备或文件 close 终止使用已打开的设备或文件 read 从输入设备或者文件中的到数据 write 将数据从应用程序存储器传到设备或文件 lseek 跳转到文件或设备中的某个指定位置 ioctl 控制设备或者用于访问设备的软件
基本I/O举例 int desc desc = open(“filename”, O_RDWR, 0) read(desc, buffer, 128) close(desc)
将Linux I/O用于TCP/IP 扩展文件描述符:可用于网络通信 扩展了read和write:可用于网络标识符 额外功能的处理,增加新的系统调用 指明本地和远端的端口(IP地址,端口号) 使用TCP还是UDP 启动传输还是等待连接 接收多少连接
小结 各厂商将TCP/IP软件集成在自己的OS中并对外提供用于软件开发的接口 接口通常采用系统调用的方式实现 软件通过接口API实现通信 TCP/IP只提供概念性接口,不规定接口细节 接口通常采用系统调用的方式实现 系统调用的过程 TCP/IP系统调用的实现的方式
本节内容 2. 客户-服务器应用模型与套接字 客服-服务器模型与软件设计 客服-服务器软件中的并发处理 TCP/IP协议的程序接口 套接字API
什么是套接字 应用程序与TCP/IP协议交互的接口 位于传输层与应用层之间 本地应用进程所创建,由操作系统所控制的通信接口(“门”) 应用进程仅使用这个接口与外界通信 可视为虚连接(屏蔽了传输层及一下各层的细节) 由应用程序 开发者控制 process TCP/UDP or others Host A socket process TCP/UDP or others Host B socket socket connection (virtual connection) internet 由操作系统控制
TCP编程架构 服务端 客户端 开始 开始 socket() socket() bind() connect() listen() 三次握手 listen() accept() send() 发送数据 recv() recv() 读取数据 send() close() 关闭连接 close() 结束 结束
创建套接字 功能:创建一个套接字,返回套接字描述符,-1表示出错 参数说明: #include <sys/types.h> #include <sys/socket.h> int socket(int domain, int type, int protocol) 功能:创建一个套接字,返回套接字描述符,-1表示出错 参数说明: domain: 通信域,socket()根据该参数选择通信协议族 PF_INET代表IPv4, PF_INET6代表IPv6, PF_IPX代表IPX协议 type: 通信类型 SOCKET_STREAM,流式套接字,TCP协议 SOCKET_DGRAM,数据报套接字,UDP协议 protocol: 指定某个协议的特定类型,一般为0 举例: int ss = socket(PF_INET, SOCK_STREAM, 0);
创建套接字 函数socket并不总是成功,返回错误码: 值 含义 EACCES 没有权限建立指定domain的type的socket EAFNOSUPPORT 不支持所给的地址类型 EINVAL 不支持此协议或者协议不可用 EMFILE 进程文件表溢出 ENFILE 已经达到系统允许打开的文件数量,打开文件过多 ENOBUFS/ENOMEM 内存不足 EPROTONOSUPPORT 指定的type在domain中不存在 其他 int ss = socket(AF_INET, SOCK_STREAM, 0); if (ss < 0) { /* 出错 */ perror("socket error\n"); return -1; }
系统创建套接字过程 int sock = socket(AF_INET, SOCK_STREAM, 0); 应用层 内核层 int sock = sys_socket(AF_INET, SOCK_STREAM, 0); struct socket sock; retval = sock_create(AF_INET, SOCK_STREAM, 0, &sock); int retval; retval = sock_map_fd(sock);
套接字描述符 OS将文件描述符实现为一个指针数组,每个指针指向一个内部的数据结构 套接字描述符在Linux/Unix系统中,等同于文件描述符 在Windows中采用一个独立的 描述符表 (一个进程一张) 用于文件0的内部数据结构 0: 用于文件1的内部数据结构 1: 2: 用于文件2的内部数据结构 3: 用于文件3的内部数据结构 .
套接字描述符 OS将文件描述符实现为一个指针数组,每个指针指向一个内部的数据结构 套接字描述符在Linux/Unix系统中,等同于文件描述符 在Windows中采用一个独立的 描述符表 (一个进程一张) Family: PF_INET 0: Service: SOCK_STREAM 1: Local IP: 2: Remote IP: 3: Local port: 4: . Remote port: :
绑定一个地址端口对 int bind(int sockfd, const struct sockaddr *my_addr, int addrlen) 功能:为套接字指明一个本地协议地址,0成功,-1出错 服务器使用该函数来指明孰知的端口号,然后等待客户连接 参数说明: sockfd: 套接字描述符,由socket()生成 my_addr: 本地地址 IP地址和端口号,可以指定其中之一,甚至一个也不指定。 统配地址:INADDR_ANY addrlen: 地址长度
套接字地址结构 sockaddr不方便设置,实际常使用sockaddr_in struct sockaddr { /* 套接字地址结构 */ sa_family_t sa_family; /* 协议族 */ char sa_data[14]; /* 协议族数据 */ } sockaddr不方便设置,实际常使用sockaddr_in struct sockaddr_in { /* 以太网地址结构 */ u8 sin_len; /* 结构长度 */ u8 sin_family; /* 通常为AF_INET */ u16 sin_port; /* 16位的端口号 */ struct in_addr sin_addr; /* IP地址32位, u32 */ char sin_zero[8]; /* 未用 */ } 两种地址结构大小完全一致,常用sockaddr_in进行设置,然后强制转换成sockaddr类型。
套接字地址结构 参数选取 struct sockaddr_in my_addr; my_addr.sin_family = AF_INET; IP地址 端口 结果 通配地址 内核选择IP地址和端口号 非0 内核选择IP地址,进程指定端口 本地IP 进程指定IP地址,内核选择端口 进程指定IP地址和端口 struct sockaddr_in my_addr; my_addr.sin_family = AF_INET; my_addr.sin_port = htons(8888); my_addr.sin_addr.s_addr = INADDR_ANY; bzero(&(my_addr.sin_zero), 8); bind(ss, (struct sockaddr *) &my_addr, sizeof(my_addr));
监听本地端口 int listen(int sockfd, int backlog) 功能:将套接字设置为被动模式,准备接收传入连接 服务器使用该函数等待客户连接 参数说明: sockfd: 套接字描述符 backlog: 等待队列的最大长度 内核维护两个队列:未完成连接队列,已完成连接队列 两个队列的和不能超过backlog 举例: int ss = listen(ss, 20);
接收网络请求 int accept(int sockfd, struct sockaddr *addr, socklent_t *addrlen) 功能:获取传入连接请求,返回新的连接的套接字描述符 为每个新的连接请求创建一个新的套接字,服务器对新的连接使用该套接字,原来的监听套接字接受其他的连接请求 新的连接上传输的数据使用新的套接字,使用完毕,服务器将关闭这个套接字 参数说明: sockfd: 套接字描述符,指明正在监听的套接字 addr: 提出请求的主机地址 addrlen: 地址长度 举例: new_sockfd = accept(ss, &addr, &addrlen);
连接目标网络服务器 int connect(int sockfd, struct sockaddr *server_addr addr, int addrlen) 功能:与远程服务器建立主动连接,0成功,-1失败 客户端使用,主动连接服务器 TCP:三次握手 UDP:设置参数 参数说明: sockfd: 套接字描述符 server_addr: 指明远程地址,IP地址和端口号 addrlen: 地址长度 举例: connect(ss, &addr, sizeof(addr));
发送数据 int send(int sockfd, const void *data, int data_len, unsigned int flags) 功能:发送数据,成功返回发送数据长度,出错返回-1 send会将数据复制到OS内核中,不需等待发送完毕再返回 参数说明: sockfd: 套接字描述符 data: 指向要发送数据的指针 data_len: 要发送数据的长度 flags: 一直为0 举例: send (ss, req, strlen(req), 0);
读取数据 int recv(int sockfd, void *buf, int buf_len, unsigned int flags) 功能:接收数据,成功返回接收数据长度,出错返回-1 如果没有数据将阻塞 对于TCP来说,最大返回缓冲区长度 对于UDP来说,如果数据长度超过缓冲区,多余数据将被抛弃 参数说明: sockfd: 套接字描述符 buf: 指向内存缓冲区的指针 buf_len: 内存缓冲区大小,以字节为单位 flags: 一直为0 举例: recv(ss, buf, 8192, 0);
发送数据报 int sendto(int sockfd, const void *data, int data_len, unsigned int flags, struct sockaddr *remaddr, int remaddr_len) 功能:基于UDP发送数据报,返回实际发送长度,出错返回-1 参数说明: sockfd: 套接字描述符 data: 指向要发送数据的指针 data_len: 要发送数据的长度 flags: 一直为0 remaddr: 远端地址:IP地址和端口号 remaddr_len: 地址长度
读取数据报 int recvfrom(int sockfd, void *buf, int buf_len, unsigned int flags, struct sockaddr *from, int from_len) 功能:从UDP接收数据,成功返回接收数据长度,出错返回-1 参数说明: sockfd: 套接字描述符 buf: 指向内存缓冲区的指针 buf_len: 内存缓冲区大小,以字节为单位 flags: 一般为0 from: 远端地址,IP地址和端口号 fromlen: 远端地址长度
关闭套接字 int close(int sockfd) 功能:关闭套接字 参数说明: 举例: close(ss); 如果只有一个进程使用,立即终止连接并撤销该套接字,如果多个进程共享套接字,将引用数减一,如果引用数降到0,则撤销,内核释放相关资源。 参数说明: sockfd: 套接字描述符 举例: close(ss);
转换函数 网络字节序 有些套接字API要求参数按照网络字节序存储 需要网络字节序和本地主机字节序转换的函数 最高位字节在前 如sockaddr_in 需要网络字节序和本地主机字节序转换的函数 要求使用,便于一致 分为16位(short)和32位(long)两种 htons:将一个短整型数从本地字节序转换为网络字节序 ntohs:将一个短整型数从网络字节序转换为本地字节序 htonl和ntohl:类似
WinSock编程 每个WinSock应用必须加载WinSock DLL的相应版本,加载WinSock通过WSAStartup()函数实现 int WSAStartup( WORD wVersionRequested, LPWSADATA lpWSAData ); 第一个参数:指明所要使用的winsock版本号 第二个参数:用于返回请求Socket的版本信息 #include <winsock2.h> #defineWSVERS MAKEWORD(2,0) ... WSADATA wsadata; if (WSAStartup(WSVERS, &wsadata)) errexit("WSAStartup failed\n");
WinSock编程 应用程序在使用完Socket库后,调用WSACleanup()函 数来解除与Socket库的绑定并且释放Socket库所占用的 系统资源 int WSACleanup(void); 使用closesocket()关闭套接字,并释放分配给该套接字的资源,涉及一个正在进行的连接,则该连接被释放 BOOL closesocket(SOCKET s) 需引入文件 头文件:Winsock2.h 库文件:WS2_32.LIB 动态库:WS2_32.DLL
Thank You !