socket套接字编程预备知识

我们知道“IP+端口号”标识网络通讯中的一个唯一的进程,这里我们说”IP+端口号“就是socket.
介绍一下网络字节序的知识,我们都知道内存中的多字节数据相对于内存地址来说有大端和小端之分,磁盘文件中的多字节数据相对于文件偏移来说也有大端和小端之分,同样网络数据流也有大端和小端之分,发送主机把发送缓冲区中的数据按照内存地址从低到高的顺序发送出去,接收主机把接收到的数据按照内存地址从低到高的顺序保存起来。因此网络数据流的地址应该这样规定:先发出的数据是低地址,后发出的数据是高地址。TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节。端口号是1000(0x3e8),则地址0是0x03,地址1是0xe8, 也就是先发0x03,再发0xe8,这16位在发送主机的缓冲区中也应该是低地址存0x03,高地址 存0xe8。但是,如果发送主机是小端字节序的,这16位被解释成0xe803,因此,发送主机把1000填到发送缓冲区之前需要做字节序的转换。同样地,接收主机如果是小端字节序的, 接到16位的源端口号也要做字节序的转换。如果主机是大端字节序的,发送和接收都不需要做转换。
为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调 用以下库函数做网络字节序和主机字节序的转换:
这里写图片描述
分析socket地址的相关类型及相关函数
这里写图片描述
IPv4地址用sockaddr_in结构体表示,IPv6地址用sockaddr_in6结构体表示,UNIX Domain Socket用sockaddr_un结构体表示。各 种socket地址结构体的开头都是相同的,前16位表⽰示整个结构体的长度(并不是所有
UNIX的实现 都有长度字段,如Linux就没有),后16位表⽰示地址类型。IPv4、IPv6和UNIX。Domain Socket的地 址类型分别定义为AF_INET、AF_INET6、AF_UNIX。这样,只要取得某种sockaddr结构体的 首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的 内容。因此,socket API可以接受各种类型的sockaddr结构体指针做参数,例 如bind、accept、connect等函数,这些函数的参数应该设计成void 类型以便接受各种类型的指 针,但是sock API的实现早于ANSI C标准化,那时还没有void 类型,因此这些函数的参数都 用struct sockaddr *类型表示,在传递参数之前要强制类型转换一下。

下面对IPv4的socket网络编程加以介绍。
sockaddr_in中的成员struct in_addr sin_addr表示32位的IP 地址。但是我们通常用点分〸十进制的字符串表示IP 地址,以下函数可以在字符串表示 和in_addr表示之间转换。
这里写图片描述
TCP协议通信流程:
这里写图片描述
通信过程叙述如下:
服务器调用socket()、bind()、listen() 完成初始化后,调用accept()阻塞等待,处于监听端口的状态,客户端调用socket()初始化后,调用connect()发出SYN段并阻塞等待服务器应答,服务器应答一个SYN-ACK段,客户端收到后从connect()返回,同时应答一个ACK段,服务器收到后 从accept()返回。
数据传输的过程: 建立连接后,TCP协议提供全双工的通信服务,但是一般的客户端/服务器程序的流程是由客户端主动发起请求,服务器被动处理请求,一问一答的方式。因此,服务器从accept()返回后立刻调 用read(),socket就像读管道一样,如果没有数据到达就阻塞等待,这时客户端调用write()发送 请求给服务器,服务器收到后从read()返回,对客户端的请求进行处理,在此期间客户端调 用read()阻塞等待服务器的应答,服务器调用write()将处理结果发回给客户端,再次调用read()阻塞 等待下一条请求,客户端收到后从read()返回,发送下一条请求,如此循环下去。如果客户端没有更多的请求了,就调用close() 关闭连接,就像写端关闭的管道一样,服务器 的read()返回0,这样服务器就知道客户端关闭了连接,也调用close()关闭连接。注意,任何一方调用close() 后,连接的两个传输方向都关闭,不能再发送数据了。如果一方调用shutdown() 则连接处于半关闭状态,仍可接收对方发来的数据。
在学习socket API时要注意应用程序和TCP协议层是如何交互的: 应用程序调用某个socket函数 时TCP协议层完成什么动作,比如调用connect()会发出SYN段 应用程序如何知道TCP协议层的状 态变化,比如从某个阻塞的socket函数返回就表明TCP协议收到了某些段,再比如read()返回0就表 明收到了FIN段。
TCP简单编程,让我们先来了解一下其中用到的函数:
1.创建socket套接字
创建套接字其实就是创建一个文件描述符

#include<sys/types.h>
#include<sys/socket.h>
int socket(int domain,int type,int protocol);

socket()打开一个网络通讯端口,如果成功的话,就像open()一样返回一个文件描述符,应用程序可以像读写文件一样用read/write在网络上收发数据,如果socket()调用出错则返回-1。对 于IPv4,family参数指AF_INET。对于TCP协议,type参数指定为SOCK_STREAM,表示面向流的传输协议。如果是UDP协议,则type参数指定为SOCK_DGRAM,表示面向数据报的传输协 议。protocol参数的介绍从略,指定为0即可。
2.绑定IP与端口号

#include<sys/types.h>
#include<sys/socket.h>
int bind(int sockfd,const struct sockaddr *addr,socklen_t addrlen);

服务器程序所监听的网络地址和端口号通常是固定不变的,客户端程序得知服务器程序的地址和端 口号后就可以向服务器发起连接,因此服务器需要调用bind绑定个固定的网络地址和端口号。bind()成功返回0,失败返回-1。bind()的作用是将参数sockfd和addr绑定在一起,使sockfd这个用于网络通讯的文件描述符监听addr所描述的地址和端口号。前面讲过,struct sockaddr *是一个通用指针类型,addr参数实际上可以接受多种协议的sockaddr结构体,而它们的长度各不相同,所以需要第三个参 数addrlen指定结构体的长度。
参数中的‘struct sockaddr’ 结构体,是sock地址的泛型化接口,我们使用IPV4协议,使用的sockaddr接口应该是‘struct sockaddr_in’,因此在使用的时候需要进行地址的强制转化。
这里写图片描述
3.监听函数

#include<sys/types.h>
#include<sys/socket.h>
int listen(int sockfd,int backlog);

listen()声明sockfd处于监听状态,并且最多允许有backlog个客户端处于
连接等待状态,如果接收到更多的连接请求就忽略。listen()成功返回0,失败返回-1。
4.接受连接函数

#include<sys/types.h>
#include<sys/socket.h>
int accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen);

三方握手完成后,服务器调用accept()接受连接,如果服务器调accept()时还没有客户端的连接请 求,就阻塞等待直到有客户端连接上来。caddr是一个传出参数,accept()返回时传出客户端的地 址和端口号。addrlen参数是一个传入传出参数,传入的是调用者提供的 缓冲区addr 的长度以避免缓冲区溢出问题,传出的是客户端地址结构体的实际长度
5.客户端连接函数

#include<sys.types.h>
#include<sys/socket.h>
int connect(int sockfd,const struct sockaddr *addr,socklen_t addrlen);

其参数与bind一样,只不过bind函数中的地址是自己的地址,connect的地址是对方的地址。

函数分析完,下篇我就给出代码的编写。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值