网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket。
建立网络通信连接至少要一对端口号(socket)。socket本质是编程接口(API),对TCP/IP的封装,TCP/IP也要提供可供程序员做网络开发所用的接口,这就是Socket编程接口;HTTP是轿车,提供了封装或者显示数据的具体形式;Socket是发动机,提供了网络通信的能力。
Socket的英文原义是“孔”或“插座”。作为BSD UNIX的 机制,取后一种意思。通常也称作" ",用于描述IP地址和端口,是一个通信链的句柄,可以用来实现不同虚拟机或不同计算机之间的通信。在Internet上的 一般运行了多个服务软件,同时提供几种服务。每种服务都打开一个Socket,并绑定到一个端口上,不同的端口对应于不同的服务。Socket正如其英文原义那样,像一个多孔插座。一台主机犹如布满各种插座的房间,每个插座有一个编号,有的插座提供220伏交流电, 有的提供110伏交流电,有的则提供有线电视节目。 客户软件将插头插到不同编号的插座,就可以得到不同的服务。
连接过程
(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,对应于无连接的 服务应用。
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);
参数说明:
s ocket:一个标识已连接 的描述字。
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(); } }}
C
服务端(Server):
#include#include #pragma comment(lib,"ws2_32.lib")void main(){WSADATA wsaData;SOCKET sockServer;SOCKADDR_IN addrServer;SOCKET sockClient;SOCKADDR_IN addrClient;WSAStartup(MAKEWORD(2,2),&wsaData);sockServer=socket(AF_INET,SOCK_STREAM,0);addrServer.sin_addr.S_un.S_addr=htonl(INADDR_ANY);//INADDR_ANY表示任何IPaddrServer.sin_family=AF_INET;addrServer.sin_port=htons(6000);//绑定端口6000bind(sockServer,(SOCKADDR*)&addrServer,sizeof(SOCKADDR));//Listen监听端listen(sockServer,5);//5为等待连接数目printf("服务器已启动:\n监听中...\n");int len=sizeof(SOCKADDR);char sendBuf[100];//发送至客户端的字符串char recvBuf[100];//接受客户端返回的字符串//会阻塞进程,直到有客户端连接上来为止sockClient=accept(sockServer,(SOCKADDR*)&addrClient,&len);//接收并打印客户端数据recv(sockClient,recvBuf,100,0);printf("%s\n",recvBuf);//关闭socketclosesocket(sockClient);WSACleanup();}
客户端 (Client):
#include#include #pragma comment(lib,"ws2_32.lib")void main(){WSADATA wsaData;SOCKET sockClient;//客户端SocketSOCKADDR_IN addrServer;//服务端地址WSAStartup(MAKEWORD(2,2),&wsaData);//新建客户端socketsockClient=socket(AF_INET,SOCK_STREAM,0);//定义要连接的服务端地址addrServer.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");//目标IP(127.0.0.1是回送地址)addrServer.sin_family=AF_INET;addrServer.sin_port=htons(6000);//连接端口6000//连接到服务端connect(sockClient,(SOCKADDR*)&addrServer,sizeof(SOCKADDR));//发送数据char message[20]="HelloSocket!";send(sockClient,message,strlen(message)+1,0);//关闭socketclosesocket(sockClient);WSACleanup();}
PHP
有强大的Socket操作能力,它的处理方式更接近于C,但是没有C的繁琐。可以看作是对C操作的Socket的一个封装。
开启一个socket监听示例程序:
C#
public class XmlSocket{//异步socket侦听从客户端传来的数据public static string data=null;//Threadsignal.线程用一个指示是否将初始状态设置为终止的布尔值初始化ManualResetEvent类的新实例。public static ManualResetEvent allDone=new ManualResetEvent(false);static void Main(string[]args){ StartListening();}public static void StartListening(){//传入数据缓冲byte[] bytes = new Byte[1024];//建立本地端口IPAddress ipAddress;String ipString = ConfigurationManager.AppSettings.Get("SocketIP");if(ipString == null || ipString == String.Empty){IPHostEntry ipHostInfo = Dns.GetHostEntry(Dns.GetHostName());ipAddress = ipHostInfo.AddressList[0];}else{ipAddress = IPAddress.Parse(ipString);}int port;String portString = ConfigurationManager.AppSettings.Get("SocketPort");if(portString == null || portString == String.Empty){port=11001;}else{port=int.Parse(portString);}IPEndPoint localEndPoint = new IPEndPoint(ipAddress, port);Socket listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);//绑定端口和数据listener.Bind(localEndPoint);listener.Listen(100);while(true){//设置无信号状态的事件allDone.Reset();//重启异步连接listener.BeginAccept(new AsyncCallback(AcceptCallback),listener);//等待连接创建后继续allDone.WaitOne();}public static void AcceptCallback(IAsyncResult ar){//接受回调方法。该方法的此节向主应用程序线程发出信号,让它继续处理并建立与客户端的连接allDone.Set();//获取客户端请求句柄Socket listener = (Socket)ar.AsyncState;Socket handler = listener.EndAccept(ar);StateObject state = new StateObject();state.workSocket = handler;handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReadCallback),state);}//与接受回调方法一样,读取回调方法也是一个AsyncCallback委托。该方法将来自客户端套接字的一个或多个字节读入数据缓冲区,然后再次调用BeginReceive方法,直到客户端发送的数据完成为止。从客户端读取整个消息后,在控制台上显示字符串,并关闭处理与客户端的连接的服务器套接字。public static void ReadCallback(IAsyncResult ar){String content = String.Empty;//创建自定义的状态对象StateObject state = (StateObject)ar.AsyncState;Socket handler = state.workSocket;//处理的句柄//读出int bytesRead = handler.EndReceive(ar);if(bytesRead>0){String len = Encoding.UTF8.GetBytes(result).Length.ToString().PadLeft(8,'0');log.writeLine(len);Send(len + result, handler);}}private static void Send(string data, Socket handler){byte[] byteData = Encoding.UTF8.GetBytes(data);handler.BeginSend(byteData, 0, byteData.Length, 0, new AsyncCallback(SendCallback), handler);}private static void SendCallback(IAsyncResult ar){Socket handler = (Socket)ar.AsyncState;//向远端发送数据int bytesSent = handler.EndSend(ar);StateObject state = new StateObject();state.workSocket = handler;handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReadCallback),state);handler.Shutdown(SocketShutdown.Both);handler.Close();}public static void StopListening(){allDone.Close();log.close();}
插槽类型
Socket AM2,原称“Socket M2”,是供 桌上型处理器使用的CPU插座,用以取代 和939,并已于2006年5月23日推出。它拥有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、支持四通道 。
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及一些其他厂商的产品。此类接口目前已被淘汰,只有部分老产品才能见到。