网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket。
建立网络通信连接至少要一对端口号(socket)。socket本质是编程接口(API),对TCP/IP的封装,TCP/IP也要提供可供程序员做网络开发所用的接口,这就是Socket编程接口;HTTP是轿车,提供了封装或者显示数据的具体形式;Socket是发动机,提供了网络通信的能力。
Socket的英文原义是“孔”或“插座”。作为BSD UNIX的 进程通信机制,取后一种意思。通常也称作" 套接字",用于描述IP地址和端口,是一个通信链的句柄,可以用来实现不同虚拟机或不同计算机之间的通信。在Internet上的 主机一般运行了多个服务软件,同时提供几种服务。每种服务都打开一个Socket,并绑定到一个端口上,不同的端口对应于不同的服务。Socket正如其英文原义那样,像一个多孔插座。一台主机犹如布满各种插座的房间,每个插座有一个编号,有的插座提供220伏交流电, 有的提供110伏交流电,有的则提供有线电视节目。 客户软件将插头插到不同编号的插座,就可以得到不同的服务。
举例说明:
Socket又称"套接字",应用程序通常通过"套接字"向网络发出请求或者应答网络请求。
以J2SDK-1.3为例,Socket和ServerSocket类库位于java.net包中。ServerSocket用于服务器端,Socket是建立网络连接时使用的。在连接成功时,应用程序两端都会产生一个Socket实例,操作这个实例,完成所需的会话。对于一个网络连接来说,套接字是平等的,并没有差别,不因为在服务器端或在客户端而产生不同级别。不管是Socket还是ServerSocket它们的工作都是通过SocketImpl类及其子类完成的。
现象解释
Socket非常类似于电话插座。以一个国家级电话网为例,电话的通话双方相当于相互通信的2个进程,区号是它的 网络地址;区内一个单位的交换机相当于一台 主机,主机分配给每个用户的局内号码相当于Socket号。任何用户在通话之前,首先要占有一部电话机,相当于申请一个Socket;同时要知道对方的号码,相当于对方有一个固定的Socket。然后向对方拨号呼叫,相当于发出连接请求(假如对方不在同一区内,还要拨对方区号,相当于给出网络地址)。假如对方在场并空闲(相当于通信的另一主机开机且可以接受连接请求),拿起电话话筒,双方就可以正式通话,相当于连接成功。双方通话的过程,是一方向电话机发出信号和对方从电话机接收信号的过程,相当于向Socket发送数据和从socket接收数据。通话结束后,一方挂起电话机相当于关闭Socket,撤消连接。
在电话系统中,一般用户只能感受到本地电话机和对方电话号码的存在,建立通话的过程,话音传输的过程以及整个电话系统的技术细节对他都是透明的,这也与Socket机制非常相似。Socket利用网间网通信设施实现 进程通信,但它对通信设施的细节毫不关心,只要通信设施能提供足够的通信能力,它就满足了。
至此,我们对Socket进行了直观的描述。抽象出来,Socket实质上提供了进程通信的端点。进程通信之前,双方首先必须各自创建一个端点,否则是没有办法建立联系并相互通信的。正如打电话之前,双方必须各自拥有一台电话机一样。
在网间网内部,每一个Socket用一个半相关描述:(协议,本地地址,本地端口)。
一个完整的Socket有一个本地唯一的Socket号,由操作系统分配。
最重要的是,Socket是面向客户/ 服务器模型而设计的,针对客户和服务器程序提供不同的Socket 系统调用。客户随机申请一个Socket(相当于一个想打电话的人可以在任何一台入网电话上拨号呼叫),系统为之分配一个Socket号;服务器拥有全局公认的Socket,任何客户都可以向它发出连接请求和信息请求(相当于一个被呼叫的电话拥有一个呼叫方知道的电话号码)。
Socket利用客户/服务器模式巧妙地解决了进程之间建立通信连接的问题。服务器Socket半相关为全局所公认非常重要。读者不妨考虑一下,两个完全随机的用户进程之间如何建立通信?假如通信双方没有任何一方的Socket固定,就好比打电话的双方彼此不知道对方的电话号码,要通话是不可能的。
连接过程
根据连接启动的方式以及本地 套接字要连接的目标,套接字之间的连接过程可以分为三个步骤: 服务器监听,客户端请求,连接确认。
(1)服务器监听:是服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态。
(2)客户端请求:是指由客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和 端口号,然后就向服务器端套接字提出连接请求。
(3)连接确认:是指当服务器端套接字监听到或者说接收到客户端套接字的连接请求,它就响应客户端套接字的请求,建立一个新的线程,把 服务器端套接字的描述发给客户端,一旦客户端确认了此描述,连接就建立好了。而服务器端 套接字继续处于 监听状态,继续接收其他客户端套接字的连接请求。
常用函数
创建
函数原型:
int socket(int
domain, int
type, int
protocol);
参数说明:
domain:协议域,又称协议族(family)。常用的协议族有AF_INET、AF_INET6、AF_LOCAL(或称AF_UNIX,Unix域Socket)、AF_ROUTE等。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。
type:指定Socket类型。常用的socket类型有SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等。流式Socket(SOCK_STREAM)是一种面向连接的Socket,针对于面向连接的TCP服务应用。数据报式Socket(SOCK_DGRAM)是一种无连接的Socket,对应于无连接的 UDP服务应用。
protocol:指定协议。常用协议有IPPROTO_TCP、IPPROTO_UDP、IPPROTO_STCP、IPPROTO_TIPC等,分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议。
注意:1.type和protocol不可以随意组合,如SOCK_STREAM不可以跟IPPROTO_UDP组合。当第三个参数为0时,会自动选择第二个参数类型对应的默认协议。
2.WindowsSocket下
protocol参数中不存在IPPROTO_STCP
返回值:
如果调用成功就返回新创建的 套接字的描述符,如果失败就返回INVALID_SOCKET(Linux下失败返回-1)。套接字描述符是一个整数类型的值。每个进程的进程空间里都有一个套接字描述符表,该表中存放着套接字描述符和套接字数据结构的对应关系。该表中有一个字段存放新创建的套接字的描述符,另一个字段存放套接字数据结构的地址,因此根据套接字描述符就可以找到其对应的套接字数据结构。每个进程在自己的进程空间里都有一个套接字描述符表但是套接字数据结构都是在操作系统的 内核缓冲里。
绑定
函数原型:
int bind(SOCKET
socket, const struct sockaddr*
address, socklen_t
address_len);
参数说明:
socket:是一个 套接字描述符。
address:是一个sockaddr结构 指针,该结构中包含了要结合的地址和 端口号。
address_len:确定address 缓冲区的长度。
返回值:
如果函数执行成功,返回值为0,否则为SOCKET_ERROR。
接收
函数原型:
int recv(SOCKET
socket, char FAR*
buf, int
len, int
flags);
参数说明:
socket:一个标识已连接 套接口的描述字。
buf:用于接收数据的 缓冲区。
len:缓冲区长度。
flags:指定调用方式。取值:MSG_PEEK 查看当前数据,数据将被复制到缓冲区中,但并不从输入队列中删除;MSG_OOB 处理 带外数据。
返回值:
若无错误发生,recv()返回读入的字节数。如果连接已中止,返回0。否则的话,返回SOCKET_ERROR错误,应用程序可通过WSAGetLastError()获取相应 错误代码。
函数原型:
ssize_t recvfrom(int
sockfd, void
buf, int
len, unsigned int
flags, struct socketaddr*
from, socket_t*
fromlen);
参数说明:
sockfd:标识一个已连接 套接口的描述字。
buf:接收 数据缓冲区。
len:缓冲区长度。
flags:调用操作方式。是以下一个或者多个标志的组合体,可通过or操作连在一起:
(1)MSG_DONTWAIT:操作不会被阻塞;
(2)MSG_ERRQUEUE: 指示应该从套接字的错误队列上接收错误值,依据不同的协议,错误值以某种辅佐性消息的方式传递进来,使用者应该提供足够大的缓冲区。导致错误的原封包通过msg_iovec作为一般的数据来传递。导致错误的数据报原目标地址作为msg_name被提供。错误以sock_extended_err结构形态被使用。
(3)MSG_PEEK:指示数据接收后,在接收队列中保留原数据,不将其删除,随后的读操作还可以接收相同的数据。
(4)MSG_TRUNC:返回封包的实际长度,即使它比所提供的缓冲区更长, 只对packet套接字有效。
(5)MSG_WAITALL:要求阻塞操作,直到请求得到完整的满足。然而,如果捕捉到信号,错误或者连接断开发生,或者下次被接收的数据类型不同,仍会返回少于请求量的数据。
(6)MSG_EOR:指示记录的结束,返回的数据完成一个记录。
(7)MSG_TRUNC:指明数据报尾部数据已被丢弃,因为它比所提供的缓冲区需要更多的空间。
/*(MSG_TRUNC使用错误,4才是MSG_TRUNC的正确解释)*/
(8)MSG_CTRUNC:指明由于缓冲区空间不足,一些控制数据已被丢弃。
(9)MSG_OOB:指示接收到out-of-band数据(即需要优先处理的数据)。
(10)MSG_ERRQUEUE:指示除了来自套接字错误队列的错误外,没有接收到其它数据。
from:(可选) 指针,指向装有源地址的缓冲区。
fromlen:(可选)指针,指向from缓冲区长度值。
发送
函数原型:
int sendto( SOCKET
s, const char FAR*
buf, int
size, int
flags, const struct sockaddr FAR*
to, int
tolen);
参数说明:
s: 套接字
buf:待发送数据的缓冲区
size:缓冲区长度
flags:调用方式标志位, 一般为0, 改变Flags,将会改变Sendto发送的形式
addr:(可选) 指针,指向目的套接字的地址
tolen:addr所指地址的长度
返回值:
如果成功,则返回发送的字节数,失败则返回SOCKET_ERROR。
接收连接请求
函数原型:
int accept( int
fd, struct socketaddr*
addr, socklen_t*
len);
参数说明:
fd:套接字描述符。
addr:返回连接着的地址
len:接收返回地址的缓冲区长度
返回值:
成功返回客户端的文件描述符,失败返回-1。
实例
JAVA
服务端(Server)
package com.socket;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class SocketServer {
public static String _pattern = "yyyy-MM-dd HH:mm:ss SSS";
public static SimpleDateFormat format = new SimpleDateFormat(_pattern);
// 设置超时间
public static int _sec = 0;
public static void main(String[] args) {
System.out.println("----------Server----------");
System.out.println(format.format(new Date()));
ServerSocket server;
try {
server = new ServerSocket(8001);
System.out.println("监听建立 等你上线\n");
Socket socket = server.accept();
System.out.println(format.format(new Date()));
System.out.println("建立了链接\n");
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
socket.setSoTimeout(_sec * 1000);
System.out.println(format.format(new Date()) + "\n" + _sec + "秒的时间 快写\n");
System.out.println(format.format(new Date()) + "\nClient:" + br.readLine() + "\n");
Writer writer = new OutputStreamWriter(socket.getOutputStream());
System.out.println(format.format(new Date()));
System.out.println("我在写回复\n");
writer.write("收到\n");
Thread.sleep(10000);
writer.flush();
System.out.println(format.format(new Date()));
System.out.println("写完啦 你收下\n\n\n\n\n");
} catch (SocketTimeoutException e) {
System.out.println(format.format(new Date()) + "\n" + _sec + "秒没给我数据 我下啦\n\n\n\n\n");
e.printStackTrace();
} catch (SocketException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
}
客户端 (Client)
package com.socket.v3;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class SocketClient {
public static String _pattern = "yyyy-MM-dd HH:mm:ss SSS";
public static SimpleDateFormat format = new SimpleDateFormat(_pattern);
// 设置超时间
public static int _sec = 5;
public static void main(String[] args) {
System.out.println("----------Client----------");
Socket socket = null;
try {
// 与服务端建立连接
socket = new Socket("127.0.0.1", 8001);
socket.setSoTimeout(_sec * 1000);
System.out.println(format.format(new Date()));
System.out.println("建立了链接\n");
// 往服务写数据
Writer writer = new OutputStreamWriter(socket.getOutputStream());
System.out.println(format.format(new Date()));
System.out.println("我在写啦\n");
Thread.sleep(10000);
writer.write("有没有收到\n");
System.out.println(format.format(new Date()));
System.out.println("写完啦 你收下\n");
writer.flush();
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
System.out.println(format.format(new Date()) + "\n" + _sec + "秒的时间 告诉我你收到了吗\n");
System.out.println(format.format(new Date()) + "\nServer:" + br.readLine());
} catch (SocketTimeoutException e) {
System.out.println(format.format(new Date()) + "\n" + _sec + "秒没收到回复 我下啦\n\n\n\n\n");
e.printStackTrace();
} catch (SocketException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
}
插槽类型
Socket AM2,原称“Socket M2”,是供 AMD桌上型处理器使用的CPU插座,用以取代 Socket 754和939,并已于2006年5月23日推出。它拥有940针,支援双通道 DDR2 SDRAM,但针脚的排列方式与 Socket 940不相同,又因为S940不支持DDR2 SDRAM,因此两者并不兼容。
[1]
LGA 1150 插槽,是 Intel 即将发布的第八代产品所采用的插槽,支持CPU新核心 Haswell ,不兼容原有 IVB、SNB 核心的CPU,新版将于2013年发布。
LGA 1155
[2] 插槽,是 Intel 平台 7/6 系列主板采用的插槽,即 Intel Ivy Bridge 核心、Sandy Bridge(新Celeron、新Pentium、第二、三代 i3/i5/i7 处理器采用)。
LGA 2011
[3] ,又称Socket R,是 英特尔(Intel)Sandy Bridge-EX微架构 CPU所使用的CPU接口。LGA2011接口将取代LGA1366接口,成为Intel最新的 旗舰产品。
LGA2011接口有2011个触点,将包含以下新特性:
1、处理器最高可达八核。
2、支持四通道 DDR3内存。
3、支持PCI-E 3.0规范。
4、 芯片组使用单芯片设计,支持两个SATA 3Gbps和多达十个SATA/SAS 6Gbps接口。
LGA 1567,又称为Socket LS,是Intel所推出的处理器插座,用于多路(多处理器)服务器上。其插座有1567个金属接触针脚,对应处理器上有1567个金属触点。于 2010 年 3 月发布,基于Intel Nehalem架构,核心代号“Beckton”的Intel Xeon-7500系列和Intel Xeon-6500系列的处理器。
Socket 939插槽,是Athlon64处理器所采用的接口类型, 针脚数为939针。支持 Socket 939 处理器的主板只需要4层 PCB。使用普通DDR内存。
Socket 940插槽,是Athlon64处理器所采用的接口类型,针脚数为940针。Socket 940接口的处理器支持双通道ECC内存,支持 Socket 940 处理器的主板必须采用6至9层PCB,必须采用带ECC校验的DDR内存。
Socket 754插槽,是Athlon64处理器所采用的接口类型,针脚数为754针。Socket 754 接口处理器支持单通道内存
LGA 775插槽,是Intel 925X Express和Intel 915 Express 芯片组,所采用的接口类型,支持Pentium 4和Pentium 4 Extreme Edition处理器, 针脚数为775针。
Socket 478插槽是旧型号Pentium 4系列处理器所采用的接口类型,针脚数为478针。Socket 478的Pentium 4处理器面积很小,其 针脚排列极为紧密。采用Socket 478插槽的主板产品数量众多,是20世纪应用最为广泛的插槽类型。
Socket A接口,也叫Socket 462,是目前AMD公司Athlon XP和Duron处理器的插座标准。Socket A接口具有462插空,可以支持133MHz 外频。如同Socket 370一样,降低了制造成本,简化了结构设计。
Socket 423插槽是最初Pentium 4处理器的标准接口,Socket 423的外形和前几种Socket类的插槽类似,对应的CPU 针脚数为423。Socket 423插槽多是基于Intel 850芯片组主板,支持1.3GHz~1.8GHz的Pentium 4处理器。不过随着DDR内存的流行, 英特尔又开发了支持SDRAM及DDR内存的i845 芯片组,CPU插槽也改成了Socket 478,Socket 423插槽也就销声匿迹了。
Socket 370架构是英特尔开发出来代替SLOT架构,外观上与Socket 7非常像,也采用零插拔力插槽,对应的CPU是370 针脚。
Socket 370主板多为采用Intel ZX、BX、i810芯片组的产品,其他厂商有VIA Apollo Pro系列、SIS 530系列等。最初认为,Socket 370的CPU升级能力可能不会太好,所以Socket 370的销量总是不如SLOT 1接口的主板。但在 英特尔推出的“铜矿”和”图拉丁”系列CPU, Socket 370接口的主板一改低端形象,逐渐取代了SLOT 1接口。目前市场中还有极少部分的主板采用此种插槽。
SLOT 1是英特尔公司为取代Socket 7而开发的CPU接口,并申请的专利。这样其它厂商就无法生产SLOT 1接口的产品,也就使得AMD、VIA、SIS等公司不得不联合起来,对Socket 7接口升级,也得到了Super 7接口。后来随着Super 7接口的兴起,英特尔又将SLOT 1结构主板的制造授权提供给了VIA、SIS、ALI等主板厂商,所以这些厂商也相应推出了采用SLOT 1接口的系列主板,丰富了主板市场。
SLOT 1是 英特尔公司为Pentium Ⅱ系列CPU设计的插槽,其将Pentium Ⅱ CPU及其相关控制电路、 二级缓存都做在一块子卡上,多数Slot 1主板使用100MHz 外频。SLOT 1的技术结构比较先进,能提供更大的内部传输 带宽和CPU性能。采用SLOT 1接口的主板芯片组有Intel的BX、i810、i820系列及VIA的Apollo系列,ALI 的Aladdin Pro Ⅱ系列及SIS的620、630系列等。此种接口已经被淘汰,市面上已无此类接口的主板产品。
SLOT 2用途比较专业,都采用于高端 服务器及 图形工作站的系统。所用的CPU也是很昂贵的Xeon( 至强)系列。Slot 2与Slot 1相比,有许多不同。首先,Slot 2插槽更长,CPU本身也都要大一些。其次,Slot 2能够胜任更高要求的多用途计算处理,这是进入高端企业计算市场的关键所在。在当时标准服务器设计中,一般厂商只能同时在系统中采用两个 Pentium Ⅱ处理器,而有了Slot 2设计后,可以在一台服务器中同时采用 8个处理器。而且采用Slot 2接口的Pentium Ⅱ CPU都采用了当时最先进的0.25微米制造工艺。支持SLOT 2接口的主板芯片组有440GX和450NX。
SLOT A接口类似于英特尔公司的SLOT 1接口,供AMD公司的K7 Athlon使用的。在技术和性能上,SLOT A主板可完全兼容原有的各种外设扩展卡设备。它使用的并不是Intel的P6 GTL+总线协议,而是Digital公司的Alpha总线协议EV6。EV6架构是种较先进的架构,它采用多线程处理的点到点 拓扑结构,支持200MHz的 总线频率。支持SLOT A接口结构的主板 芯片组主要有两种,一种是AMD的AMD 750芯片组,另一种是VIA的Apollo KX133芯片组。此类接口已被Socket A接口全面取代。
Socket 7:Socket在英文里就是插槽的意思,Socket 7也被叫做Super 7。最初是 英特尔公司为Pentium MMX系列CPU设计的插槽,后来英特尔放弃Socket 7接口转向SLOT 1接口,AMD、VIA、ALI、SIS等厂商仍然沿用此接口,直至发展出Socket A接口。该插槽基本特征为321插孔,系统使用66MHz的总线。Super 7主板增加了对100MHz 外频和AGP接口类型的支持。
Super 7采用的 芯片组有VIA公司的MVP3、MVP4系列,SIS公司的530/540系列及ALI的Aladdin V系列等主板产品。对应Super 7接口CPU的产品有AMD K6-2、K6-Ⅲ 、Cyrix M2及一些其他厂商的产品。此类接口目前已被淘汰,只有部分老产品才能见到。
文章内容援引自 https://baike.baidu.com/item/socket/281150?fr=aladdin