套接字原理----socket

运行在不同机器上的进程彼此通过向套接字发送报文来进行通信。每个进程好比是一座房子,进程的套接字就好比是一个门。套接字是应用进程和TCP之间的门,应用程序开发者可以控制套接字的应用层那一侧所有的东西,但是不能控制运输层那一侧。

服务器为了能对客户机程序发起连接作出响应,应满足:

第一、服务器程序不能处于休眠状态;

第二、服务器程序必须有某种套接字。

socket通信流程:

1、服务器根据地址类型(ipv4,ipv6)、socket类型、协议创建socket

2、服务器为socket绑定ip地址和端口号

3、服务器socket监听端口号请求,随时准备接收客户端发来的连接,这时候服务器的socket并没有被打开

4、客户端创建socket

5、客户端打开socket,根据服务器ip地址和端口号试图连接服务器socket

6、服务器socket接收到客户端socket请求,被动打开,开始接收客户端请求,直到客户端返回连接信息。这时候socket进入阻塞状态,所谓阻塞即accept()方法一直到客户端返回连接信息后才返回,开始接收下一个客户端谅解请求

7、客户端连接成功,向服务器发送连接状态信息

8、服务器accept方法返回,连接成功

9、客户端向socket写入信息

10、服务器读取信息

11、客户端关闭

12、服务器端关闭

1、创建一个套接字---socket()

  socket()函数用于根据指定的地址族,数据类型和协议来分配一个套接字的描述字及其所用的资源。

#include <sys/socket.h>

  int socket (int domain, int type, int protocol);

  domain:表示套接字要使用的协议族,协议族在“linux/socket.h”里有详细的定义,常用的协议族:

<1>:AF_UNIX(本机通信)

<2>:AF_INET(TCP/IP-IPv4)DOS、Windows中仅支持AF_INET

<3>:AF_INET6(TCP/IP-IPv6)

  type:套接字类型,常用类型

<1>:SOCK_STREAM(TCP流:提供了面向连接、可靠数据传输服务,数据无差错。无重复地发送,且按发送顺序接收。内设流量控制,避免数据流超限;数据被看做是字节流,无长度限制,文件传送协议(FTP)即使用流式套接字)

<2>:SOCK_DGRAM(UDP数据报:提供了无连接服务。数据报以独立包的形式被发送,不提供无错保证,数据可能会丢失或重复,并且接收混乱。网络文件系统(NFS)使用数据报式套接字)

<3>:SOCK_RAW(原始套接字:该接口允许对较低层协议,如IP、ICMP直接访问。常用于检验新的协议实现或访问现有服务中配置的新设备)

protocol:如果调用者不希望特别指定使用的协议,一般设置为0,使用默认的连接方式。

socket是一个函数,那么他也有返回值,当套接字成功创建时,返回套接字,失败返回“-1”;错误代码则写入“ERRNO”中;

创建套接字:

#include <sys/type.h>

#include <sys/socket.h>

#include <linux/socket.h>

int soc_fd_tcp;

int soc_fd_udp;

soc_fd_tcp = socket(AF_INET, SOCK_STREAM,0);

soc_fd_udp = socket(AF_INET, SOCK_DGRAM, 0);

if(soc_fd_tcp < 0)

{

perror("TCP SOCKET ERROR\n");

exit(-1);

}

if(soc_fd_udp < 0)

{

perror("UDP SOCKET ERROR\n");

exit(-1);

}

不同的应用程序对应不同的socket,那么socket里到底是什么?

答:socket套接字地址!套接字地址是一个结构体,以TCP传输协议作为例子,套接字地址这个数据结构里面包含了:地址类型、端口号、IP地址、填充字节这4中数据,原型为:

#include <netinet/in.h>

struct sockaddr_in

{

unsigned short sin_family;

unsigned short int sin_port;

struct in_addr sin_addr;

unsigned char sin_zero[8];

};

其中:

sin_family: 表示地址类型,对于基于TCP/IP传输协议的通信,该值只能是AF_INET;

sin_port:表示端口号,用来辨别本地通讯进程,由于每个进程都有自己的端口号,因此在通讯之前必须要分配一个没有被访问的端口号。

sin_add:表示32位的IP地址;

sin_zero:表示填充字节,一般情况下该值为0;

socket数据的赋值:

struct sockaddr_in sin;//设置一个sin套接字地址

sin.sin_family = AF_INET;//它基于TCP/IP协议,因此,sin_family的值为AF_INET

sin.sin_port = htons(80); //htons是端口函数,表示端口号为80

sin.sin_addr.s_addr = inet_addr("202.96.134.133");//sin_addr是一个结构体

memset(sin.sin_zero, 0, sizeof(sin.sin_zero));//给数组sin_zero清零

sin_addr结构体原型:

struct in_addr

{

unsigned long s_addr;

};

2、指定本地地址---bind()函数:

将本地地址与一套接字捆绑。当socket()创建套接字后,它便存在于一个名字空间(地址族)中,bind()函数通过给一个未命名的套接字分配一个本地名字,来为套接字建立本地捆绑(主机地址/端口号)。

例如对应AF_INET、AF_INET6就是把一个ipv4或ipv6地址和端口号组合赋值给socket。

int bin(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

函数的三个参数分别为:

sockfd:即socket描述字,它是通过socket()函数创建的,唯一表示一个socket。bind()函数就是给这个描述字绑定一个名字。

addr:一个const struct sockaddr* 指针,指向要绑定给sockfd的协议地址。这个地址结构,根据地址创建socket时的地址协议族的不同而不同。如IPv4:

struct sockaddr_in

{

sa_family_t    sin_family; /* address family: AF_INET */

in_port_t      sin_port;   /* port in network byte order */

struct in_addr sin_addr;   /* internet address */

};

/* Internet address. */

struct in_addr

{

uint32_t       s_addr;     /* address in network byte order */

};

ipv6对应的是:

struct sockaddr_in6

{

sa_family_t     sin6_family;   /* AF_INET6 */

in_port_t       sin6_port;     /* port number */

uint32_t        sin6_flowinfo; /* IPv6 flow information */

struct in6_addr sin6_addr;     /* IPv6 address */

uint32_t        sin6_scope_id; /* Scope ID (new in 2.4) */

};

struct in6_addr

{

unsigned char   s6_addr[16];   /* IPv6 address */

};

addrlen:表示对应的地址长度。

通常服务器在启动的时候都会绑定一个众所周知的地址(IP地址+端口号),用于提供服务,客户就可以通过它来连接服务器;而客户端就不用指定,系统会自动分配一个端口号和自身IP地址组合。这就是为什么服务器端会在listen之前会调用bind(),而客户端就不会调用,而是在connect()时由系统随机生成一个。

3、监听和请求连接---listen()、connect()函数:

作为服务器,在调用socket()、bind()之后就会调用listen()来监听这个socket(),作为客户端,这时应该调用connect(),发出连接请求,服务器端就会接收到这个请求

int listen(int sockfd, int backlog);

sockfd表示:本地已建立、尚未连接的套接字号,服务器会在他的上面接收请求backlog表示请求连接队列的最大长度,就是告诉套接字在忙于处理上一个请求时还可以接受多少个请求进入,换句话来说,这决定挂起连接的队列的最大大小,用于限制排队请求的个数,目前允许的最大值是5.如果没有发生错误,listen()返回0,否则,返回:SOCKET_ERROR。

listen()在执行调用过程中可为没有调用过的bind()的套接字sockfd完成所必须的连接,并建立长度为backlog的请求连接队列。

调用listen()是服务器接收一个连接请求的四个步骤中的第三步。它在调用socket()分配一个流套接字,且调用bind()给sockfd赋值于一个名字之后,调用!而且要在accept()之前调用。

  int connect(int sockfd, const struct sockaddr* addr, socklen_t addrlen);

第一个参数即为:客户端的socket描述字;

第二个参数为服务器的socket的地址;

第三个参数为socket地址的长度,客户端通过调用connect函数与服务器来建立TCP连接。

4、建立套接字连接---accept()函数:

accept()用于面向连接服务器。

TCP服务器端一次调用socket()、bind()、listen()之后,就会监听指定的socket()地址了,TCP客户端依次调用socket()、connect()之后就向TCP服务器发送一个连接请求,TCP服务器监听到这个请求之后,就会调用accept()函数来接收请求,这样连接便建立好了,之后就可以网络I/O操作了,即类同于普通文件的读写I/O操作。

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

accept函数:

第一个参数为服务器的socket描述字;

第二个参数为指向struct sockaddr* 的指针,用于返回客户端的协议地址;

第三个参数为协议地址的长度。如果accept成功,那么其返回值是由内核自动生成的一个全新的描述字,代表与返回客户的TCP连接。

accept(),参数addr和addrlen存放客户方的地址信息。调用前,参数addr 指向一个初始值为空的地址结构,而addrlen 的初始值为0;调用accept()后,服务器等待从编号为sockfd的套接字上接受客户连接请求,而连接请求是由客户方的connect()调用发出的。当有连接请求到达时,accept()调用将请求连接队列上的第一个客户方套接字地址及长度放入addr
和addrlen,并创建一个与sockfd有相同特性的新套接字号。新的套接字可用于处理服务器并发请求。

  注意:accept的第一个参数为服务器的socket描述字,是服务器开始调用socket()函数生成的,称为:监听socket描述字;而accept函数返回的是已连接的socket描述字。一个服务器通常仅仅只创建一个监听socket描述字,它在服务器的生命周期内一直存在。内核为每个服务器进程接收的客户连接创建一个已连接socket描述字,当服务器完成了对某个客户的服务,相应的连接socket描述字就被关闭。

5、数据传输---send()与recv()

int send(int sockfd, const void *buf, size_t len, int flags);

不论客户端还是服务器应用程序都用send函数来向TCP连接的另一端发送数据。客户端一般用send函数向服务器发送请求,而服务器则通常用send函数来向客户程序发送应答。

send函数:

第一个参数是指定发送端套接字描述字;

第二个参数是指明一个存放应用程序要发送数据的缓冲区,

第三个参数指明要发送的数据的字节数,

第四个参数一般置为0;

在这里描述socket的send函数的执行流程:

当调用send函数时,send先比较待发送数据的长度len和套接字sockfd的发送缓冲长度,如果len大于sockfd的发送缓冲区长度,该函数返回SOCKET_ERROR;如果len小于或者等于sockfd的发送缓冲区长度,那么send先检查协议,是否正在发送sockfd的发送缓冲区的数据,如果是,就等待协议把数据发送完,如果协议还没有开始发送sockfd的发送缓冲区的数据或者sockfd发送缓冲区没有数据,那么send就比较sockfd的发送缓冲区的剩余空间和len,如果len大于剩余空间,send就一直等待协议把sockfd的发送缓冲区的数据发送完,如果len小于剩余空间大小,send就仅仅把buf中的数据copy到剩余空间里(注意:并不是send把sockfd的发送缓冲中的数据传送到连接的另一端,而是协议传的,send仅仅是把buf中的数据copy到sockfd的发送缓冲区的剩余空间里)。如果send函数copy数据成功,就返回实际copy的字节数,如果send在copy数据时出现错误,那么send就返回SOCK_ERROR;如果send在等待协议传送数据时网络断开的话,那么send函数也返回SOCKET_ERROR.

要注意:send函数把buf中的数据成功copy到sockfd的发送缓冲的剩余空间里后它就返回了,但是此时这些数据并不一定马上被传到连接的另一端。如果协议在后续的传送过程中出现网络错误的话,那么下一个Socket函数就会返回SOCKET_ERROR。(每一个除send外的Socket函数在执 行的最开始总要先等待套接字的发送缓冲中的数据被协议传送完毕才能继续,如果在等待时出现网络错误,那么该Socket函数就返回
SOCKET_ERROR);

Send函数的返回值有三类:

(1)返回值=0:

(2)返回值<0:发送失败,错误原因存于全局变量errno中

(3)返回值>0:表示发送的字节数(实际上是拷贝到发送缓冲中的字节数)

int recv(int sockfd, void *buf, size_t len, int flags);

不论是客户端还是服务器端应用程序都用recv函数从TCP连接的另一端接收数据。

recv函数的:

第一个参数指定接收端套接字描述字;

第二个参数指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据;

第三个参数指明buf的长度;

第四个参数一般设置为0;

现在描述socket的recv函数的执行流程:当应用程序调用recv函数时,recv先等待sockfd发送缓冲区中的数据被协议传送完毕,如果协议再传送sockfd的发送缓冲区中的数据时出现了网络错误,那么recv函数返回SOCKET_ERROR,如果数据缓冲区中没有数据或者数据被协议成功发送完后,recv函数先检查套接字sockfd的接收缓冲区,如果sockfd的接收缓冲区中没有数据或者协议正在接收数据,那么recv就一直等待,直到协议把数据接收完毕,当协议把数据接收完毕,recv函数就把sockfd的接收缓冲中的数据copy到buf中(注意:协议接收到数据可能大于buf的长度,所以这种情况下要多次调用recv函数才能把接收缓冲中的数据copy完,recv函数仅仅是copy函数,真正接收数据是协议来完成的。recv函数返回其实际copy的字节数。如果recv在copy时出错,那么它返回SOCKET_ERROR;如果recv函数在等待协议接收数据时网络中断了,那么它返回0。

recv函数的返回值类型:

(1)成功执行时,返回接收到的字节数。

(2)若另一端已关闭连接则返回0,这种关闭是对方主动且正常的关闭

(3)失败返回-1

6、关闭套接字---close()

close()函数:关闭套接字s,并释放分配给该套接字的资源;如果s涉及一个打开的TCP连接,则该连接被释放。

在服务器与客户端建立连接之后,会进行一些读写操作,完成了读写操作就要关闭相应的socket描述字,好比操作完打开的文件要调用fclose关闭打开的文件。

#include <unistd.h>

int close(int fd);

close一个TCP socket的缺省行为时把该socket标记为以关闭,然后立即返回到调用进程。该描述字不能再由调用进程使用,也就是说不能再作为read或write的第一个参数。

注意:close操作只是使相应socket描述字的引用计数-1,只有当引用计数为0的时候,才会触发TCP客户端向服务器发送终止连接请求。

时间: 2024-11-09 05:45:21

套接字原理----socket的相关文章

java套接字(socket)实例

客户端socket 流程: 1.连接远程主机 2.发送数据 3.接收数据 4.关闭流与socket连接 实例: import java.io.*; import java.net.Socket; import java.util.Date; /** * Created by CLY on 2017/7/11. */ public class ClientSocket { public static void main(String[] arg){ int port = 233;//与之连接的服务

八、套接字(Socket)

demo 一个连接由它的两个端点标识,这样的端点称为套接 套接字是支持TCP/IP协议的网络通信的基本操作单元. 可以将套接字看作不同主机间的进程进行双向通信的端点. 上图连接1的一对套接字为: (192.168.2.23,5000)和(192.168.2.122,8888) 上图连接2的一对套接字为: (192.168.2.23,5001)和(192.168.2.122,8888) 对于UDP协议尽管两个进程之间没有建立连接,但是也同样存在发送端点,和接收端点,也同样使用套接字的概念. 套接字

linux网络编程——套接字(socket)入门

1.套接字的基本结构 struct sockaddr 这个结构用来存储套接字地址. 数据定义: struct sockaddr { unsigned short sa_family; /* address族, AF_xxx */ char sa_data[14]; /* 14 bytes的协议地址 */ }; sa_family 一般来说,都是"AFINET". sa_data 包含了一些远程电脑的地址.端口和套接字的数目,它里面的数据是杂溶在一切的. 为了处理struct socka

什么是网络套接字(Socket)?

什么是网络套接字(Socket)?一时还真不好回答,而且网络上也有各种解释,莫衷一是.下文将以本人所查阅到的资料来说明一下什么是Socket. 1. Socket定义 Socket在维基百科的定义: A network socket is an endpoint of an inter-process communication across a computer network. Today, most communication between computers is based on t

套接字(Socket)描述符的使用情况

套接字是通信端点的抽象.正如使用文件描述符访问文件,应用程序用套接字描述符访问套接字.套接字描述符在UNIX系统中被当作是一种文件描述符.事实上,许多处理文件描述符的函数(read和write)可以用于处理套接字描述符. --<unix环境高级编程> WEB应用客户端(浏览器或APP等)与服务端的通信大多数是通过HTTP或HTTPS协议进行的.而套接字(Socket)应该算是OSI七层网络模型中应用层与传输层直接的抽象层,它是面向应用层的一个编程接口.而HTTP/HTTPS则位于应用层,就是通

《Python》网络编程之客户端/服务端框架、套接字(socket)初使用

一.软件开发的机构 我们了解的涉及到两个程序之间通讯的应用大致可以分为两种: 第一种是应用类:QQ.微信.网盘等这一类是属于需要安装的桌面应用 第二种是web类:比如百度.知乎.博客园等使用浏览器访问就可以直接使用的应用 这些应用的本质其实都是两个程序之间的通讯,而这两个分类又对应了两个软件开发的架构 1.C/S架构 C/S即:Client与Server,中卫意思:客户端与服务器端架构,这种架构也是从用户层面(也可以是物理层面)来划分的. 这里的客户端一般泛指客户端应用程序EXE,程序需要先安装

网络编程-套接字(socket)

一.什么是套接字 套接字(socket)是计算机之前数据传输的工具,是有计算机系统提供的一个组件,是网络数据传输的软件设备. 二.TCP/IP协议 TCP/IP协议栈共分为4层(OSI规范分7层),tcp.udp就是基于socket的一种协议 三.套接字的分类 1.流式套接字(TCP) 它提供了一种可靠的.面向连接的双向通讯方式.适用于传输数据量大的场景. TCP的具有以下三项特征: 传输过程中数据不会丢失,面向连接的套接字会根据接收端的状态传输数据,如果传输出错还会提供重传服务 按序传输数据,

Java网络编程从入门到精通(16):客户端套接字(Socket)的超时

客户端套接字的超时(timeout)就是指在客户端通过Socket和服务器进行通讯的过程中,由于网络延迟,网络阻塞等原因,造成服务器并未及时响应客户端的一种现象.在一段时间后,客户端由于未收到服务端的响应而抛出一个超时错误; 其中客户端所等待的时间就是超时时间. 由于生产超时错误的一端都是被动端:也就是说,这一端是在接收数据,而不是发送数据.对于客户端Socket来说,只有两个地方是在接收数据:一个是在连接服务器时;另一个是在连接服务器成功后,接收服务器发过来的数据时.因此,客户端超时也分为两种

JAVA套接字(Socket)101七天系列—第四天【一个简单示例】

一个简单示例  1. 背景 我们将在本部分讨论的示例将阐明在 Java 代码中如何使用 Socket 和 ServerSocket.客户机用Socket 连接到服务器.服务器用 ServerSocket 在端口 3000 侦听.客户机请求服务器 C: 驱动器上的文件内容. 为清楚起见,我们把示例分解成客户机端和服务器端.最后我们将把它们组合起来以使您能看到整体模样. 我们在使用 JDK 1.2 的 IBM VisualAge for Java 3.5 上开发这些代码.要自己创建这个示例,您应有完