socket基础1

来源:互联网 发布:人工智能研究方向 编辑:程序博客网 时间:2024/05/22 09:04

常用的套接字接口

„Linux/Unix 下: Berkeley Socket 是最突出的一套接口。

Windows 下: Win Socket ,也称winsock

与Berkeley Socket 很类似的接口

socket接口与TCP/IP协议关系


可认为一个IP 与一端口(port )联合在一起形成一个套接字,它是网络上的一个传输接口。在网络的另外一端可有一个对应的套接字与通信。


基本概念

IP地址:IP Address我想很容易理解,就是依照TCP/IP协议分配给本地主机的网络地址,比如两个进程要通讯,任一进程要知道通讯对方的位置,位置如何来确定,就用对方的IP。
端口号:用来标识本地通讯进程,方便OS提交数据.就是说进程指定了对方进程的网络IP,但这个IP只是用来标识进程所在的主机,如何来找到运行在这个主机的这个进程呢,就用端口号。
连接:指两个进程间的通讯链路。

半相关:网络中用一个三元组可以在全局唯一标志一个进程:(协议,本地地址,本地端口号),这样一个三元组,叫做一个半相关,它指定连接的每半部分。

全相关:一个完整的网间进程通信需要由两个进程组成,并且只能使用同一种高层协议。也就是说,不可能通信的一端用TCP协议,而另一端用UDP协议。因此一个完整的网间通信需要一个五元组来标识:(协议,本地地址,本地端口号,远地地址,远地端口号),这样一个五元组,叫做一个相关(association),即两个协议相同的半相关才能组合成一个合适的相关,或完全指定组成一连接。

客户 /服务器模式

TCP/IP网络应用中,最常用的通信模式是客户/ 服务器模式(Client/Server model),即客户向服务器发出服务请求,服务器接收到请求后,提供相应的服务。
客户端/服务器模式的建立基于以下两点:首先,建立网络的起因是网络中软硬件资源、运算能力和信息不均等,需要共享,从而造就拥有众多资源的主机提供服务,资源较少的客户请求服务这一非对等作用。其次,网间进程通信完全是异步的,相互通信的进程间既不存在父子关系,又不共享内存缓冲区,因此需要一种机制为希望通信的进程间建立联系,为二者的数据交换提供同步,这就是客户端/服务器模式的TCP/IP。

客户端/服务器模式采取的是主动请求方式:首先服务器方要先启动,并根据请求提供相应服务。

客户端与服务器的连接方式主要有两种:
1)流式套接口连接 TCP
流式套接口是可靠的双向通讯的数据流。传送的包会按发送时的顺序到达。
int s=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP);
2)数据报套接口连接 UDP
使用这种方式,传送的包不一定会按发送时的顺序到达。当然每个包的内部是无错误的。
int s=socket(PF_INET,SOCK_DGRAM,IPPROTO_UDP);

服务器:

1. 服务器先要端打开一个通信通道,并告知本地主机它需要在某个端口上(如FTP 为21)接收客户请求;
2. 等待客户请求到达该端口;
3. 接收到服务请求,处理该请求并应答。直至交互完成。
4. 返回第二步,等待另一客户请求。
5. 关闭服务器。

客户端:

1. 打开一个通信通道,连接到服务器所在主机的特定端口(此时,服务器端已经在这个Socket 等待请求)
2. 向服务器发服务请求报文,等待并接收应答;继续提出请求并等待应答......
3. 请求结束后关闭通信通道并终止。


服务器端连接过程



客户端连接过程



数据传输过程



总结1:

1. 客户与服务器进程的作用是非对称的,它们各自完成的功能不同,因此编码也不同。
2. 服务进程一般是先于客户请求而启动的,启动后即在相应的Socket监听来自客户端的请求。只要系统运行,该服务进程一直存在,直到正常或强迫终止。

服务器方面初始时需要执行的操作:

„ int socket ()        建立一个Socket
„ int bind()            与某个端口绑定
„ int listen()          开始监听端口
„ int accept()        等待/ 接受客户端的连接请

 客户端需要执行的操作:

„ int socket ()      建立一个Socket
„ int connect()     连接到服务器

实例-服务器端

(1)   创建套接字

应用程序在使用套接字前,首先必须拥有一个套接字,系统调用socket()向应用程序提供创建套接字的手段。
int s=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP)
函数原型:
int socket (int domain, int type, int protocol)
功能:创建套接字。
返回值:返回值是新创建套接字的句柄,即以后引用该套接字时使用的标识符。错误时返回-1 。
type 取值:SOCK_STREAM, SOCK_DGRAM 等
protocol 取值:IPPROTO_TCP, IPPROTO_UDP, IPPROTO_IP 等
参数 domain  描述将使用的协议族。
    AF_INET : 用于表示因特网协议族,DOS、 WINDOWS中仅支持AF_INET。
    AF_UNIX : 用于表示U n i x 管道功能
参数 type 表明通信的语义。
    SOCK_STREAM :字节流服务,可理解为TCP 连接,提供了一个面向连接、可靠的数据传输服务,数据无差错、无重复地发送,且按发送顺序接收。内设流量控制,避免数                                       据流超限;数据被看作是字节流,无长度限制。文件传送协议(FTP)即使用流式套接字。
    SOCK_DGRAM:面向消息的服务,可理解为UDP 连接,数据包以独立包形式被发送,不提供无错保证,数据可能丢失或重复,并且接收顺序混乱。网络文件系统(NFS)使用                                       数据报式套接字。
    SOCK_RAW:允许对较低层协议,如IP、ICMP直接访问。常用于检验新的协议实现或访问现有服务中配置的新设备。
参数 protocol  则指明将要用到的特定协议
    IPPROTO_TCP: 指的是使用TCP 协议
    IPPROTO_UDP :指的是使用UDP 协议
socket()系统调用实际上指定了相关五元组中的“协议”这一元。

(2) 设置地址端口

#define SERVER_PORT 12345
struct sockaddr_in channel ;
memset(&channel,0,sizeof(sockaddr_in));   //初始化为0
channel.sin_family=AF_INET;
channel.sin_addr.s_addr=htonl(INADDR_ANY);
channel.sin_port=htons(SERVER_PORT);
参数中用到的数据结构:
structsockaddr_in:在因特网协议中地址描述使用的数据结构
structsockaddr_in{ 
                                 short                       intsin_family;  
                                 unsigned short     intsin_port; 
                                 struct    in_addr    sin_addr; 
                                 unsigned char      sin_zero[8]; 
                                 }; 
sa_family 描述将使用的协议族,一般为AF_INET 
sin_port 端口信息。
sin_addr long 形式的IP 地址
sin_zero[8]     填充字节

字节序

l不同的计算机系统采用不同的字节序存储数据,同样一个两字节的16位整数,在内存中存储的方式就不同:
l一种方式是将低字节存储在起始地址,称为“Little-Endian”字节序,Intel、AMD等采用的是这种方式;
l另一种是将高字节存储在起始地址,称为“Big-Endian”字节序,由Macintosh、Motorola等所采用
把给定系统所采用的字节序称为主机字节序。
为了避免不同类别主机之间在数据交换时由于对于字节序解释的不同而导致的差错,引入了网络字节序,即网络传输所采用的字节序。
规定网络字节序使用“Big-Endian”方式。
举例:有以下四个主要的转换函数:
             htons() 将Short型数据转换为网络字节类型
             htonl()  将Long型数据转换为网络字节类型
             ntohs()  将Short型数据转换为本地字节类型
             ntohl()  将Long型数据转换为本地字节类型

IP地址形式

我们经常见到IP地址格式的是a.b.c.d,但是根据不同的socket,封装的IP地址的形式是不同的,如前所述(伯克利)的IP地址封装为 in_addr形式,
两者的转换请参考相应的函数(伯克利socket)。

(3) 设置socket 属性(可选)

函数原型:int setsockopt(int s,  int level,  int optname,  const char* optval,  int optlen);

(4)绑定套接字

当一个套接字用socket()创建后,存在一个名字空间(地址族),但它没有被命名。bind()将套接字地址(包括本地主机地址和本地端口地址)与所创建的套接字号联系起来,即将名字赋予套接字,以指定本地半相关。
bind(s,(struct sockaddr * )&channel, sizeof(channel));
函数原型:
int bind(int socket, struct socketaddr *address, int addr_len)
功能:将创建的 socket 与adress (包含 IP 和port 信息)绑定。
返回值:在错误的时候会返回-1/SOCKET_ERROR,如果没有错误发生,bind()返回0。
参数 socket 描述将使用的套接字(socket函数返回值)。
参数 addr_len 描述的是参数 adress 的长度。
参数 adress 描述将绑定的地址,其长度可变,结构随通信域的不同而不同。

(5)  开始监听

此调用用于面向连接服务器,表明它愿意接收连接。listen()需在accept()之前调用。
#define QUEUE_SIZE 10
listen(s,QUEUE_SIZE);
函数原型:
int listen(int socket, int backlog)
功能:定义在指定的 Socket 上可有多少个待处理的连接。
返回值:在发生错误时返回-1 。
参数socket 是本地调用 socket()  返回的套接口文件描述符。
参数backlog  是在进入队列中允许的连接数目。
调用listen()是服务器接收一个连接请求的四个步骤中的第三步。它在调用socket()分配一个流套接字,且调用bind()给s赋于一个名字之后调用,而且一定要在accept()之前调用。

(6)接收客户端连接

int sa=accept(s,0,0);
函数原型:
int accept(int socket, struct socketaddr *address, int * addr_len)
功能:接收客户端连接请求,默认情况下是阻塞的。
返回值:如果连接成功,函数将返回一个新的套接口文件描述符。
参数socket:本地socket描述符。
参数address: 指向客户方套接字地址结构的指针,用来接收连接实体的地址。address的确切格式由套接字创建时建立的地址族决定。
参数addr_len:客户方套接字地址的长度(字节数)。
accept() 用于面向连接服务器。参数address和addr_len存放客户方的地址信息。调用前,参数addr 指向一个初始值为空的地址结构,而addrlen 的初始值为0;调用accept()后,服务器等待从编号为socket的套接字上接受客户连接请求,而连接请求是由客户方的connect()调用发出的。当有连接请求到达时,accept()调用将请求连接队列上的第一个客户方套接字地址及长度放入address和addr_len,并创建一个与s有相同特性的新套接字号。新的套接字可用于处理服务器并发请求。
接下来,就可以对这个描述符进行发送 (send())  和接收 (recv())  操作了。错误时返回-1

实例-客户端

(1)  创建socket :

同服务端一样
s=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP);

(2)获取服务器IP 地址

struct hostent * h;
h=gethostbyname(argv[1]); 
函数原型:struct hostent * gethostbyna me(const char *hostname); 
功能:据主机名查找主机的IP 。hostname是域名。
返回值:成功时返回一个指向结构体 hostent 的指针或者NULL

(3)连接服务器

该调用导致本地系统和外部系统(server)之间连接实际建立。
connect(s,(struct sockaddr *)&channel,sizeof(channel));
函数原型:
int connect(int socket, struct sockaddr *serv_addr, int addrlen)
功能:在客户端被用于连接到服务器。
返回值:发生错误的时候返回-1
参数socket:本地套接口文件描述符。
参数serv_addr:包含是服务器(对方)的地址和端口信息
参数addrlen:长度,常为 sizeof(struct sockaddr_in),对方套接字地址长度。

(4) 数据接收与发送

int read(int s, char *buff, unsigned nbytes);
int write(int s, char *buff, unsigned nbytes);
功能:分别表示对指定的文件描述符进行读定操作
返回值:读定成功时,返回一个表示读出/写入字节数的正数。返回0 表示文件尾,-1 表示读/写失败。
参数s:已连接的本地套接字描述符。
buff:指向存有发送数据的缓冲区的指针,其长度由len 指定。
nbytes:指定传输控制标志。
Win socket中,不能用read()/write()函数操作Socket ,而需要使用recv() /send()函数来操作Socket 的发送与接收。

(5)  关闭socket

close(s);
函数原型:
int close(int socket);
功能:关闭对应的套接口。
Win socket为closesocket() 

总结

四个套接字系统调用,socket()、bind()、 connect()、accept(),可以完成一个完全五元相关的建立。
socket()指定五元组中的协议元,它的用法与是否为客户或服务器、是否面向连接无关。
bind()指定五元组中的本地二元,即本地主机地址和端口号,其用法与是否面向连接有关:
        在服务器方,无论是否面向连接,均要调用 bind();
        客户端,若采用面向连接,则可以不调用bind(),而通过connect()自动完成。若采用无连接,客户方必须使用bind()以获得一个唯一的地址。

注意

        具体的函数定义等可能有误,请参考自己的开发环境。

原创粉丝点击