文章目录
- socket介绍
- java中使用socket
- 基于tcp的socket通信
- 使用ServerSocket类创建一个web服务器:(java)
- windows下的基于tcp的socket编程(c++写)
- InetAddress 类的方法
- 附录1 TCP UDP
- 附录2 websocket
socket介绍
Socket的英文原义是“孔”或“插座”。在编程中,Socket被称做 套接字,是网络通信中的一种约定。
Socket编程用于解决我们 客户端与 服务端之间通信的问题。
套接字使用TCP提供了两台计算机之间的通信机制。 客户端程序创建一个套接字,并尝试连接服务器的套接字。
当连接建立时,服务器会创建一个 Socket 对象。客户端和服务器现在可以通过对 Socket 对象的写入和读取来进行通信。
java.net.Socket 类代表一个套接字,并且java.net.ServerSocket 类为服务器程序提供了一种来监听客户端,并与他们建立连接的机制。
TCP 是一个双向的通信协议,因此数据可以通过两个数据流在同一时间发送
java中的serversocket类:用于创建Socket套接字的服务端,而Socket类的作用是创建Socket的客户端。代码层面是用Socket类去连接ServerSocket类,即客户端主动连接服务端。
每一个socket都有一个输出流和一个输入流,客户端的输出流连接到服务器端的输入流,而客户端的输入流连接到服务器端的输出流。
例:
java中使用socket
服务端:
public static void main(String[]args){
try{
ServerSocket socket = new ServerSocket(8088);
System.out.println("阻塞开始:"+System.currentTimeMillis());
socket.accept();
System.out.println("server阻塞结束:"+System.currentTimeMillis());
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
现在写客户端:
public static void main(String[]args){
try {
System.out.println("client连接准备"+System.currentTimeMillis());
Socket socket = new Socket("localhost",8088);
System.out.println("连接结束:"+System.currentTimeMillis());
socket.close();
}catch (UnknownHostException e){
e.printStackTrace();
}
catch (IOException E){
E.printStackTrace();
}
}
(只有启动客户端时才输出阻塞结束的一行,当不启动时保持阻塞状态)
new Socket(“www.csdn.net”,80);可以把参数写成其他IP地址或者域名,如果写成域名,就会使用DNS服务转成IP地址再访问服务端。
public static void main(String[]args) throws IOException {
Socket socket = null;
try {
System.out.println("client连接准备");
socket = new Socket("www.csdn.net",80);
System.out.println("连接成功:");
}catch (IOException e){
e.printStackTrace();
}
finally {
socket.close();
}
}
基于tcp的socket通信
TCP提供基于“流”的“长连接”的数据传递,发送的数据带有顺序性。TCP是一种流协议,以流为单位进行数据传输。
什么是长连接?长连接可以实现当服务端与客户端连接成功后连续地传输数据,在这个过程中,连接保持开启的状态,数据传输完毕后连接不关闭。长连接是指建立Socket连接后,无论是否使用这个连接,该连接都保持连接的状态。
什么是短连接?短连接是当服务端与客户端连接成功后开始传输数据,数据传输完毕后则连接立即关闭,如果还想再次传输数据,则需要再创建新的连接进行数据传输。
如何建立连接呢?需要客户端与服务端进行三次握手,握手成功后说明客户端与服务端能实现数据通信。UDP是无连接协议,也就是客户端与服务端没有确认彼此都存在的握手过程,因此不存在长连接与短链接概念。
1)长连接的优缺点
1)优点:除了第一次之外,客户端不需要每次传输数据时都先与服务端进行握手,这样就减少了握手确认的时间,直接传输数据,提高程序运行效率。
2)缺点:在服务端保存多个Socket对象,大量占用服务器资源。
(2)短连接的优缺点
1)优点:在服务端不需要保存多个Socket对象,降低内存占用率。
2)缺点:每次传输数据前都要重新创建连接,也就是每次都要进行3次握手,增加处理的时间。
使用ServerSocket类创建一个web服务器:(java)
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(6666);
Socket socket = serverSocket.accept();
InputStream inputStream = socket.getInputStream();
InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String getString = "";
while (!"".equals(getString = bufferedReader.readLine())) {
System.out.println(getString);
}
OutputStream outputStream = socket.getOutputStream();
outputStream.write("HTTP/1.1 200 OK\r\n\r\n".getBytes());
outputStream.write(
"<html><body>< a href=' '>i am baidu.com welcome you!</a></body></html>".getBytes());
outputStream.flush();
inputStream.close();
outputStream.close();
socket.close();
serverSocket.close();
}
(这是书上的代码,很奇怪打开浏览器后打不开)
windows下的基于tcp的socket编程(c++写)
本地TCP客户端往本地TCP服务端发送数据,TCP服务端收到数据则会打印输出,同时把原数据返回给TCP客户端。这个例子类似于我们在做单片机的串口实验时,串口上位机往我们的单片机发送数据,单片机收到数据则把该数据原样返回给上位机。
tcp_server.c:
#include <stdio.h>
#include <winsock2.h>
#define BUF_LEN 100
int main(void)
{
WSADATA wd;
SOCKET ServerSock, ClientSock;
char Buf[BUF_LEN] = {0};
SOCKADDR ClientAddr;
SOCKADDR_IN ServerSockAddr;
int addr_size = 0, recv_len = 0;
/* 初始化操作sock需要的DLL */
WSAStartup(MAKEWORD(2,2),&wd);
/* 创建服务端socket */
if (-1 == (ServerSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)))
{
printf("socket error!\n");
exit(1);
}
/* 设置服务端信息 */
memset(&ServerSockAddr, 0, sizeof(ServerSockAddr)); // 给结构体ServerSockAddr清零
ServerSockAddr.sin_family = AF_INET; // 使用IPv4地址
ServerSockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");// 本机IP地址
ServerSockAddr.sin_port = htons(1314); // 端口
/* 绑定套接字 */
if (-1 == bind(ServerSock, (SOCKADDR*)&ServerSockAddr, sizeof(SOCKADDR)))
{
printf("bind error!\n");
exit(1);
}
/* 进入监听状态 */
if (-1 == listen(ServerSock, 10))
{
printf("listen error!\n");
exit(1);
}
addr_size = sizeof(SOCKADDR);
while (1)
{
/* 监听客户端请求,accept函数返回一个新的套接字,发送和接收都是用这个套接字 */
if (-1 == (ClientSock = accept(ServerSock, (SOCKADDR*)&ClientAddr, &addr_size)))
{
printf("socket error!\n");
exit(1);
}
/* 接受客户端的返回数据 */
int recv_len = recv(ClientSock, Buf, BUF_LEN, 0);
printf("客户端发送过来的数据为:%s\n", Buf);
/* 发送数据到客户端 */
send(ClientSock, Buf, recv_len, 0);
/* 关闭客户端套接字 */
closesocket(ClientSock);
/* 清空缓冲区 */
memset(Buf, 0, BUF_LEN);
}
/*如果有退出循环的条件,这里还需要清除对socket库的使用*/
/* 关闭服务端套接字 */
//closesocket(ServerSock);
/* WSACleanup();*/
return 0;
}
客户端程序tcp_client.c
#include <stdio.h>
#include <winsock2.h>
#define BUF_LEN 100
int main(void)
{
WSADATA wd;
SOCKET ClientSock;
char Buf[BUF_LEN] = {0};
SOCKADDR_IN ServerSockAddr;
/* 初始化操作sock需要的DLL */
WSAStartup(MAKEWORD(2,2),&wd);
/* 向服务器发起请求 */
memset(&ServerSockAddr, 0, sizeof(ServerSockAddr));
ServerSockAddr.sin_family = AF_INET;
ServerSockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
ServerSockAddr.sin_port = htons(1314);
while (1)
{
/* 创建客户端socket */
if (-1 == (ClientSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)))
{
printf("socket error!\n");
exit(1);
}
if (-1 == connect(ClientSock, (SOCKADDR*)&ServerSockAddr, sizeof(SOCKADDR)))
{
printf("connect error!\n");
exit(1);
}
printf("请输入一个字符串,发送给服务端:");
gets(Buf);
/* 发送数据到服务端 */
send(ClientSock, Buf, strlen(Buf), 0);
/* 接受服务端的返回数据 */
recv(ClientSock, Buf, BUF_LEN, 0);
printf("服务端发送过来的数据为:%s\n", Buf);
memset(Buf, 0, BUF_LEN); // 重置缓冲区
closesocket(ClientSock); // 关闭套接字
}
// WSACleanup(); /*如果有退出循环的条件,这里还需要清除对socket库的使用*/
return 0;
}
下载mingw,并加入环境变量。
上述两个程序放在在bin目录下
输入
gcc tcp_server.c -o tcp_server.exe -lwsock32
gcc tcp_client.c -o tcp_client.exe -lwsock32
双击exe文件,一开始出现了乱码:
在notepad中修改编码:
使用ansi编码重新gcc编译:
再次打开exe,可以正常发送数据,乱码没了:
(本部分windows下基于tcp的通信代码来自公众号:
https://mp.weixin.qq.com/s/C895Oj15jXam10xz3Z_6_w)
测试的图片是在自己电脑上测试的。
InetAddress 类的方法
这个类表示互联网协议(IP)地址。下面列出了 Socket 编程时比较有用的方法:
1 static InetAddress getByAddress(byte[] addr)
在给定原始 IP 地址的情况下,返回 InetAddress 对象。
2 static InetAddress getByAddress(String host, byte[] addr)
根据提供的主机名和 IP 地址创建 InetAddress。
3 static InetAddress getByName(String host)
在给定主机名的情况下确定主机的 IP 地址。
4 String getHostAddress()
返回 IP 地址字符串(以文本表现形式)。
5 String getHostName()
获取此 IP 地址的主机名。
附录1 TCP UDP
TCP协议
TCP(Transmission Control Protocol 传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议,数据可以准确发送,数据丢失会重发。TCP协议常用于web应用中。TCP连接(三次握手)
TCP传输起始时,客户端、服务端要完成三次数据交互工作才能建立连接,常称为三次握手。
UDP协议
UDP(User Datagram Protocol, 用户数据报协议)是一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务,可以保证通讯效率,传输延时小。例如视频聊天应用中用的就是UDP协议,这样可以保证及时丢失少量数据,视频的显示也不受很大影响。
附录2 websocket
很多网站为了实现推送技术,所用的技术都是 Ajax 轮询。轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP请求,然后由服务器返回最新的数据给客户端的浏览器。这种传统的模式带来很明显的缺点,即浏览器需要不断的向服务器发出请求,然而HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多的带宽等资源。
HTML5 定义的 WebSocket 协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。
浏览器通过 JavaScript 向服务器发出建立 WebSocket 连接的请求,连接建立以后,客户端和服务器端就可以通过 TCP 连接直接交换数据。
当你获取 Web Socket 连接后,你可以通过 send() 方法来向服务器发送数据,并通过 onmessage 事件来接收服务器返回的数据。
以下 API 用于创建 WebSocket 对象。
var Socket = new WebSocket(url, [protocol] );
WebSocket
WebSocket协议是基于TCP的一种新的网络协议。它实现了浏览器与服务器全双工(full-duplex)通信——允许服务器主动发送信息给客户端。 WebSocket通信协议于2011年被IETF定为标准RFC 6455,并被RFC7936所补充规范。
优点
服务器可以主动传送数据给客户端
功能
实现了浏览器与服务器全双工通信
WebSocket协议支持(在受控环境中运行不受信任的代码的)客户端与(选择加入该代码的通信的)远程主机之间进行全双工通信。用于此的安全模型是Web浏览器常用的基于原始的安全模式。 协议包括一个开放的握手以及随后的TCP层上的消息帧。 该技术的目标是为基于浏览器的、需要和服务器进行双向通信的(服务器不能依赖于打开多个HTTP连接(例如,使用XMLHttpRequest或和长轮询))应用程序提供一种通信机制。
产生背景
简单的说,WebSocket协议之前,双工通信是通过多个http链接来实现,这导致了效率低下。WebSocket解决了这个问题。
长久以来, 创建实现客户端和用户端之间双工通讯的web app都会造成HTTP轮询的滥用: 客户端向主机不断发送不同的HTTP呼叫来进行询问。
这会导致一系列的问题:
1.服务器被迫为每个客户端使用许多不同的底层TCP连接:一个用于向客户端发送信息,其它用于接收每个传入消息。
2.有些协议有很高的开销,每一个客户端和服务器之间都有HTTP头。
3.客户端脚本被迫维护从传出连接到传入连接的映射来追踪回复。
一个更简单的解决方案是使用单个TCP连接双向通信。 这就是WebSocket协议所提供的功能。 结合WebSocket API ,WebSocket协议提供了一个用来替代HTTP轮询实现网页到远程主机的双向通信的方法。
WebSocket协议被设计来取代用HTTP作为传输层的双向通讯技术,这些技术只能牺牲效率和可依赖性其中一方来提高另一方,因为HTTP最初的目的不是为了双向通讯。(获得更多关于此的讨论可查阅RFC6202)
实现原理
在实现websocket连线过程中,需要通过浏览器发出websocket连线请求,然后服务器发出回应,这个过程通常称为“握手” 。在 WebSocket API,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。在此WebSocket 协议中,为我们实现即时服务带来了两大好处:
Header
互相沟通的Header是很小的-大概只有 2 Bytes
Server Push
服务器的推送,服务器不再被动的接收到浏览器的请求之后才返回数据,而是在有新数据时就主动推送给浏览器。
WebSocket 事件
以下是 WebSocket 对象的相关事件。假定我们使用了以上代码创建了 Socket 对象:
事件 事件处理程序 描述
open Socket.onopen 连接建立时触发
message Socket.onmessage 客户端接收服务端数据时触发
error Socket.onerror 通信发生错误时触发
close Socket.onclose 连接关闭时触发
方法
Socket.send()
使用连接发送数据
Socket.close()
关闭连接
为了建立一个 WebSocket 连接,客户端浏览器首先要向服务器发起一个 HTTP 请求,这个请求和通常的 HTTP 请求不同,包含了一些附加头信息,其中附加头信息"Upgrade: WebSocket"表明这是一个申请协议升级的 HTTP 请求,服务器端解析这些附加的头信息然后产生应答信息返回给客户端,客户端和服务器端的 WebSocket 连接就建立起来了,双方就可以通过这个连接通道自由的传递信息,并且这个连接会持续存在直到客户端或者服务器端的某一方主动的关闭连接。