Socket编程

什么是Socket(套接字)

  • 套接字(socket)是一个抽象层,应用程序可以通过它发送或接收数据,可对其进行像对文件一样的打开、读写和关闭等操作。套接字允许应用程序将I/O插入到网络中,并与网络中的其他应用程序进行通信。网络套接字是IP地址与端口的组合。
  • 传输层实现端到端的通信,因此,每一个传输层连接有两个端点。那么,传输层连接的端点是什么呢?不是主机,不是主机的IP地址,不是应用进程,也不是传输层的协议端口。传输层连接的端点叫做套接字(socket)。
  • Socket编程是一个针对应用编程接口(API)的编程

在这里插入图片描述

Socket抽象

  • 类似于文件的抽象
  • 当应用进程创建套接字时,操作系统分配一个数据结构存
    储该套接字相关信息
  • 返回套接字描述符
    在这里插入图片描述

地址结构

已定义结构sockaddr_in:

struct sockaddr_in
{
u_char sin_len; /*地址长度 */
u_char sin_family; /*地址族(TCP/IP:AF_INET) */
u_short sin_port; /*端口号 */
struct in_addr sin_addr; /*IP地址 */
char sin_zero[8]; /*未用(置0) */
}

Socket面向TCP/IP的服务类型

在这里插入图片描述
TCP-UDP协议



Socket API函数

WSAStartup

int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);

使用Socket的应用程序在使用Socket之前必须首先调用WSAStartup函数

两个参数:

  • 第一个参数指明程序请求使用的WinSock版本,其中高位字节指明副版本、低位字节指明主版本十六进制整数,例如0x102表示2.1版
  • 第二个参数返回实际的WinSock的版本信息指向WSADATA结构的指针

WSACleanup

int WSACleanup (void);

应用程序在完成对请求的Socket库的使用,最后要调用WSACleanup函数

  • 解除与Socket库的绑定
  • 释放Socket库所占用的系统资源

socket

sd = socket(protofamily,type,proto);
/*struct protoent *p; 
p=getprotobyname("tcp"); 
SOCKET sd=socket(PF_INET,SOCK_STREAM,p->p_proto);*/

创建套接字

  • 操作系统返回套接字描述符(sd)

  • 第一个参数(协议族): protofamily = PF_INET(TCP/IP)

  • 第二个参数(套接字类型):
    type = SOCK_STREAM,SOCK_DGRAM or SOCK_RAW(TCP/IP)

  • 第三个参数(协议号):0为默认

Closesocket

int closesocket(SOCKET sd);

关闭一个描述符为sd的套接字

  • 如果多个进程共享一个套接字,调用closesocket 将套接字引用计数减1,减至0才关闭
    一个进程中的多线程对一个套接字的使用无计数
  • 如果进程中的一个线程调用closesocket将一个套接字关闭,该进程中的其他线程也将不能访问该套接字
  • 返回值:
    0:成功
    SOCKET_ERROR:失败

bind

int bind(sd,localaddr,addrlen);

绑定套接字的本地端点地址: IP地址+端口号

  • 参数:
    套接字描述符(sd)
    端点地址(localaddr)结构sockaddr_in
  • 客户程序一般不必调用bind函数

listen

int listen(sd,queuesize);

置服务器端的流套接字处于监听状态

  • 仅服务器端调用
  • 仅用于面向连接的流套接字
  • 设置连接请求队列大小(queuesize)
  • 返回值:
    0:成功
    SOCKET_ERROR:失败

connect

connect(sd,saddr,saddrlen);

客户程序调用connect函数来使客户套接字(sd)与特定计算机的特定端口(saddr)的套接字(服务)进行连接

  • 仅用于客户端
  • 可用于TCP客户端也可以用于UDP客户端
    TCP客户端:建立TCP连接
    UDP客户端:指定服务器端点地址

accept

newsock = accept(sd,caddr,caddrlen);

服务程序调用accept函数从处于监听状态的流套接字sd的客户连接请求队列中取出排在最前的一个客户请求,并且创建一个新的套接字来与客户套接字创建连接通道

  • 仅用于TCP套接字
  • 仅用于服务器
  • 利用新创建的套接字(newsock)与客户通信



send, sendto

send(sd,*buf,len,flags);
sendto(sd,*buf,len,flags,destaddr,addrlen);

send函数TCP套接字(客户与服务器)或调用了connect函数的UDP客户端套接字
sendto函数用于UDP服务器端套接字与未调用connect函数的UDP客户端套接字



recv, recvfrom

recv(sd,*buffer,len,flags);
recvfrom(sd,*buf,len,flags,senderaddr,saddrlen);

recv函数从TCP连接的另一端接收数据,或者从调用了connect函数的UDP客户端套接字接收服务器发来的数据
recvfrom函数用于从UDP服务器端套接字与未调用connect函数的UDP客户端套接字接收对端数据



setsockopt, getsockopt

int setsockopt(int sd, int level, int optname, *optval, int optlen);
int getsockopt(int sd, int level, int optname, *optval, socklen_t *optlen);

setsockopt()函数用来设置套接字sd的选项参数
getsockopt()函数用于获取任意类型、任意状态套接口的选项当前值,并把结果存入optval



关于网络字节顺序

  • TCP/IP定义了标准的用于协议头中的二进制整数表示:网络字节顺序(network byte order)
  • 某些Socket API函数的参数需要存储为网络字节顺序(如IP地址、端口号等)
  • 可以实现本地字节顺序与网络字节顺序间转换的函数
    htons: 本地字节顺序→网络字节顺序(16bits)
    ntohs: 网络字节顺序→本地字节顺序(16bits)
    htonl: 本地字节顺序→网络字节顺序(32bits)
    ntohl: 网络字节顺序→本地字节顺序(32bits)

网络应用的Socket API(TCP)调用基本流程

在这里插入图片描述


客户端软件设计


TCP客户端软件流程

  1. 确定服务器IP地址与端口号
  2. 创建套接字
  3. 分配本地端点地址(IP地址+端口号)
  4. 连接服务器(套接字)
  5. 遵循应用层协议进行通信
  6. 关闭/释放连接

UDP客户端软件流程

  1. 确定服务器IP地址与端口号
  2. 创建套接字
  3. 分配本地端点地址(IP地址+端口号)
  4. 指定服务器端点地址,构造UDP数据报
  5. 遵循应用层协议进行通信
  6. 关闭/释放套接字

解析服务器IP地址

struct hostent {
char FAR* h_name; /*official host name */
char FAR* FAR* h_aliases; /*other aliases */
short h_addrtype; /*address type */
short h_lengty; /*address length */
char FAR* FAR* h_addr_list; /*list of address */
};
#define h_addr h_addr_list[0]
  • 客户端可能使用域名(如:study.163.com)或IP地址(如:123.58.180.121)标识服务器
  • IP协议需要使用32位二进制IP地址
  • 需要将域名或IP地址转换为32位IP地址
  • 函数inet_addr( ) 实现点分十进制IP地址到32位IP地址转换
  • 函数gethostbyname( ) 实现域名到32位IP地址转换,返回一个指向结构hostent 的指针

解析服务器(熟知)端口号

struct servent {
char FAR* s_name; /*official service name */
char FAR* FAR* s_aliases; /*other aliases */
short s_port; /*port for this service */
char FAR* s_proto; /*protocol to use */
};
  • 客户端还可能使用服务名(如HTTP)标识服务器端口
  • 需要将服务名转换为熟知端口号
  • 函数getservbyname( ) 返回一个指向结构servent

解析协议号

struct protoent {
char FAR* p_name; /*official protocol name */
char FAR* FAR* p_aliases; /*list of aliases allowed*/
short p_proto; /*official protocol number*/
};
  • 客户端可能使用协议名(如:TCP)指定协议

  • 需要将协议名转换为协议号(如:6)
    函数getprotobyname ( ) 实现协议名到协议号的转换返回一个指向结构protoent的指针


服务器软件设计


4种类型基本服务器

  • 循环无连接(Iterative connectionless)服务器
  • 循环面向连接(Iterative connection-oriented)服务器
  • 并发无连接(Concurrent connectionless)服务器
  • 并发面向连接(Concurrent connection-oriented)服务器

循环无连接服务器基本流程

  1. 创建套接字
  2. 绑定端点地址(INADDR_ANY+端口号)
  3. 反复接收来自客户端的请求
  4. 遵循应用层协议,构造响应报文,发送给客户

循环面向连接服务器基本流程

  1. 创建(主)套接字,并绑定熟知端口号;
  2. 设置(主)套接字为被动监听模式,准备用于服务器;
  3. 调用accept()函数接收下一个连接请求(通过主套接字),创建新套接字用于与该客户建立连接;
  4. 遵循应用层协议,反复接收客户请求,构造并发送响应(通过新套接字);
  5. 完成为特定客户服务后,关闭与该客户之间的连接,返回步骤3.

并发无连接服务器基本流程

  • 主线程1: 创建套接字,并绑定熟知端口号;
  • 主线程2: 反复调用recvfrom()函数,接收下一个 客户请求,并创建新线程处理该客户响应;
  • 子线程1: 接收一个特定请求;
  • 子线程2: 依据应用层协议构造响应报文,并调用 sendto()发送;
  • 子线程3: 退出(一个子线程处理一个请求后即终 止)。

并发面向连接服务器基本流程

  • 主线程1: 创建(主)套接字,并绑定熟知端口号;
  • 主线程2: 设置(主)套接字为被动监听模式,准 备用于服务器;
  • 主线程3: 反复调用accept()函数接收下一个连接 请求(通过主套接字),并创建一个新 的子线程处理该客户响应;
  • 子线程1: 接收一个客户的服务请求(通过新创建 的套接字);
  • 子线程2: 遵循应用层协议与特定客户进行交互;
  • 子线程3: 关闭/释放连接并退出(线程终止)

sendto

retcode=sendto(socket,data,length,flags,destaddr,addrlen);

在这里插入图片描述
recvfrom

retcode=recvfrom(socket,buf,length,flags,from,fromlen);

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值