初识Socket用法
liebian365 2025-01-05 18:33 16 浏览 0 评论
什么是socket?
Socket的英文原义是“孔”或“插座”。在网络编程中,网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket。
Socket套接字是通信的基石,是支持TCP/IP协议的网络通信的基本操作单元。它是网络通信过程中端点的抽象表示,包含进行网络通信必须的五种信息:连接使用的协议,本地主机的IP地址,本地进程的协议端口,远地主机的IP地址,远地进程的协议端口。
Socket本质是编程接口(API),对TCP/IP的封装,TCP/IP也要提供可供程序员做网络开发所用的接口,这就是Socket编程接口;HTTP是轿车,提供了封装或者显示数据的具体形式;Socket是发动机,提供了网络通信的能力。
Socket的原理
Socket实质上提供了进程通信的端点。进程通信之前,双方首先必须各自创建一个端点,否则是没有办法建立联系并相互通信的。正如打电话之前,双方必须各自拥有一台电话机一样。
套接字之间的连接过程可以分为三个步骤:服务器监听,客户端请求,连接确认。
- 服务器监听:是服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态。
- 客户端请求:是指由客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。
- 连接确认:是指当服务器端套接字监听到或者说接收到客户端套接字的连接请求,它就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户端,一旦客户端确认了此描述,连接就建立好了。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。
TCP与UDP的差别
UDP:
- 每个数据报中都给出了完整的地址信息,因此无需要建立发送方和接收方的连接。
- UDP传输数据时是有大小限制的,每个被传输的数据报必须限定在64KB之内。
- UDP是一个不可靠的协议,发送方所发送的数据报并不一定以相同的次序到达接收方
TCP:
- 面向连接的协议,在socket之间进行数据传输之前必然要建立连接,所以在TCP中需要连接时间。
- TCP传输数据没有大小限制,一旦连接建立起来,双方的socket就可以按统一的格式传输大的数据。
- TCP是一个可靠的协议,它确保接收方完全正确地获取发送方所发送的全部数据。
应用:
- TCP在网络通信上有极强的生命力,例如远程连接(Telnet)和文件传输(FTP)都需要不定长度的数据被可靠地传输。但是可靠的传输是要付出代价的,对数据内容正确性的检验必然占用计算机的处理时间和网络的带宽,因此TCP传输的效率不如UDP高。
- UDP操作简单,而且仅需要较少的监护,因此通常用于局域网高可靠性的分散系统中client/server应用程序。例如视频会议系统,并不要求音频视频数据绝对的正确,只要保证连贯性就可以了,这种情况下显然使用UDP会更合理一些。
功能齐全的Socket,都要包含以下基本结构,其工作过程包含以下四个步骤:
- 创建Socket;
- 打开连接到Socket的输入/出流;
- 按照一定的协议对Socket进行读/写操作;
- 关闭Socket。
服务端:
public class Server {
/**
* java.net.ServerSocket
* ServerSocket是运行在服务端上的。其主要有两个作用
* 1:向服务端申请服务端口(客户端Socket就是通过这个端口与服务端建立连接的)
* 2:监听服务端口,一旦客户端连接会立即常见一个Socket,通过该Socket与客户端交互
*
* 如果我们将Socket比喻为"电话",那么ServerSocket相当于"总机"
*/
private ServerSocket serverSocket;
// private PrintWriter[] allOut = {};
private Collection<PrintWriter> allOut = new ArrayList<>();
public Server(){
try {
/*
ServerSocket在创建的时候要申请一个固定的端口号,客户端才能通过这个
端口建立连接。
如果该端口被当前操作系统中其他程序使用了,那么这里实例化会抛出异常:
java.net.BindException:address already in use
绑定异常:地址被使用了
*/
System.out.println("正在启动服务端...");
serverSocket = new ServerSocket(8088);
System.out.println("服务端启动完毕!");
} catch (IOException e) {
e.printStackTrace();
}
}
public void start(){
try {
/*
ServerSocket的accept方法是一个阻塞方法。
开始等待客户端的连接,一旦一个客户端通过端口建立连接,此时accept方法
会立即返回一个Socket实例。通过该实例可以与该客户端进行交互。
相当于是接电话的动作。
阻塞方法:调用后,程序就"卡住"不往下执行了。
*/
while(true) {
System.out.println("等待客户端连接");
Socket socket = serverSocket.accept();
System.out.println("一个客户端连接了!");
//启动一个线程来处理该客户端的交互
//Client:客户端 Handler:处理器
ClientHandler clientHandler = new ClientHandler(socket);
Thread thread = new Thread(clientHandler);
thread.start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Server server = new Server();
server.start();
}
/**
* 该线程任务是用一个线程来处理一个客户端的交互工作
*/
private class ClientHandler implements Runnable{
private Socket socket;
private String host;//记录远端计算机的地址信息
public ClientHandler(Socket socket){
this.socket = socket;
host = socket.getInetAddress().getHostAddress();
}
public void run(){
PrintWriter pw = null;
try {
//通过socket获取输入流读取对方发送过来的消息
InputStream in = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(in, StandardCharsets.UTF_8);
BufferedReader br = new BufferedReader(isr);
//通过socket获取输出流用于给对方发送消息
OutputStream out = socket.getOutputStream();
OutputStreamWriter osw
= new OutputStreamWriter(out, StandardCharsets.UTF_8);
BufferedWriter bw = new BufferedWriter(osw);
pw = new PrintWriter(bw,true);
//将该输出流存入共享数组allOut中
synchronized (Server.this) {
//1扩容allOut
// allOut = Arrays.copyOf(allOut, allOut.length + 1);
//2将pw放到数组最后一个格子里
// allOut[allOut.length - 1] = pw;
allOut.add(pw);
}
//通知所有客户端,该用户上线了!
sendMessage(host+"上线了,当前在线人数:"+allOut.size());
String line;
/*
这里的BufferedReader读取时低下连接的流是通过Socket获取的输入流,
当远端计算机还处于连接状态,但是暂时没有发送内容时,readLine方法会
处于阻塞状态,直到对方发送过来一行字符串为止。
如果返回值为null,则表示对方断开了连接(对方调用了socket.close())。
对于windows的客户端而言,如果是强行杀死的进程,服务端这里readLine方法
会抛出下面异常:
java.net.SocketException: connection reset
服务端无法避免这个异常。
*/
while ((line = br.readLine()) != null) {
//遍历allOut数组,将消息发送给所有客户端
sendMessage(host+"说:" + line);
}
}catch(IOException e){
// e.printStackTrace();
}finally {
//处理客户端断开连接后的操作
synchronized (Server.this) {
//将pw从数组allOut中删除
allOut.remove(pw);
}
sendMessage(host+"下线了,当前在线人数:"+allOut.size());
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 将消息群发给所有客户端
* @param line
*/
private void sendMessage(String line){
synchronized (Server.this) {
System.out.println(line);
//遍历allOut数组,将消息发送给所有客户端
for (PrintWriter pw : allOut) {
pw.println(line);
}
}
}
}
}
客户端
public class Client {
/*
java.net.Socket 插座 套接字
Socket封装了TCP协议的通讯细节,使用它可以和服务端建立TCP连接,并基于两个流的
读写完成数据交换。
*/
private Socket socket;
/**
* 构造方法,用于初始化客户端
*/
public Client(){
try {
/*
实例化Socket时常用的构造方法:
Socket(String host,int port)
这个构造器实例化Socket的过程就是与服务端建立连接的过程。
参数1:服务端的IP地址
参数2:服务端开启的服务端口
我们通过服务端的IP可以找到网络上服务端所在的计算机。通过端口号可以找到
该机器上的服务端应用程序从而与之建立连接。
*/
//本机IP地址的写法可以是“localhost”
System.out.println("正在连接服务端...");
socket = new Socket("localhost",8088);
System.out.println("与服务端建立连接了!");
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 客户端开始工作的方法
*/
public void start(){
try {
//启动用于读取服务端发送过来消息的线程
ServerHandler handler = new ServerHandler();
Thread t = new Thread(handler);
t.start();
/*
通过socket获取的字节输出流写出的字节会通过网络发送给远端计算机
*/
OutputStream out = socket.getOutputStream();
OutputStreamWriter osw
= new OutputStreamWriter(out, StandardCharsets.UTF_8);
BufferedWriter bw = new BufferedWriter(osw);
PrintWriter pw = new PrintWriter(bw,true);
Scanner scanner = new Scanner(System.in);
while(true) {
String line = scanner.nextLine();
if("exit".equalsIgnoreCase(line)){
break;
}
pw.println(line);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
/*
Socket提供了close方法,可以与远端计算机断开连接。
该方法调用时,也会自动关闭通过它获取的输入流和输出流。
*/
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Client client = new Client();
client.start();
}
/**
* 该线程任务负责处理服务端发送过来的消息
*/
private class ServerHandler implements Runnable{
public void run(){
try{
//通过socket获取输入流读取服务端发送过来的消息
InputStream in = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(in, StandardCharsets.UTF_8);
BufferedReader br = new BufferedReader(isr);
String line;
//循环读取服务端发送过来的每一行字符串
while((line = br.readLine())!=null){
System.out.println(line);
}
}catch(IOException e){
}
}
}
}
以上就是简单认识Socket,及一个简单的客户端与服务端通信的小例子。
学习记录,如有侵权请联系删除。
相关推荐
- 4万多吨豪华游轮遇险 竟是因为这个原因……
-
(观察者网讯)4.7万吨豪华游轮搁浅,竟是因为油量太低?据观察者网此前报道,挪威游轮“维京天空”号上周六(23日)在挪威近海发生引擎故障搁浅。船上载有1300多人,其中28人受伤住院。经过数天的调...
- “菜鸟黑客”必用兵器之“渗透测试篇二”
-
"菜鸟黑客"必用兵器之"渗透测试篇二"上篇文章主要针对伙伴们对"渗透测试"应该如何学习?"渗透测试"的基本流程?本篇文章继续上次的分享,接着介绍一下黑客们常用的渗透测试工具有哪些?以及用实验环境让大家...
- 科幻春晚丨《震动羽翼说“Hello”》两万年星间飞行,探测器对地球的最终告白
-
作者|藤井太洋译者|祝力新【编者按】2021年科幻春晚的最后一篇小说,来自大家喜爱的日本科幻作家藤井太洋。小说将视角放在一颗太空探测器上,延续了他一贯的浪漫风格。...
- 麦子陪你做作业(二):KEGG通路数据库的正确打开姿势
-
作者:麦子KEGG是通路数据库中最庞大的,涵盖基因组网络信息,主要注释基因的功能和调控关系。当我们选到了合适的候选分子,单变量研究也已做完,接着研究机制的时便可使用到它。你需要了解你的分子目前已有哪些...
- 知存科技王绍迪:突破存储墙瓶颈,详解存算一体架构优势
-
智东西(公众号:zhidxcom)编辑|韦世玮智东西6月5日消息,近日,在落幕不久的GTIC2021嵌入式AI创新峰会上,知存科技CEO王绍迪博士以《存算一体AI芯片:AIoT设备的算力新选择》...
- 每日新闻播报(September 14)_每日新闻播报英文
-
AnOscarstatuestandscoveredwithplasticduringpreparationsleadinguptothe87thAcademyAward...
- 香港新巴城巴开放实时到站数据 供科技界研发使用
-
中新网3月22日电据香港《明报》报道,香港特区政府致力推动智慧城市,鼓励公私营机构开放数据,以便科技界研发使用。香港运输署21日与新巴及城巴(两巴)公司签署谅解备忘录,两巴将于2019年第3季度,开...
- 5款不容错过的APP: Red Bull Alert,Flipagram,WifiMapper
-
本周有不少非常出色的app推出,鸵鸟电台做了一个小合集。亮相本周榜单的有WifiMapper's安卓版的app,其中包含了RedBull的一款新型闹钟,还有一款可爱的怪物主题益智游戏。一起来看看我...
- Qt动画效果展示_qt显示图片
-
今天在这篇博文中,主要实践Qt动画,做一个实例来讲解Qt动画使用,其界面如下图所示(由于没有录制为gif动画图片,所以请各位下载查看效果):该程序使用应用程序单窗口,主窗口继承于QMainWindow...
- 如何从0到1设计实现一门自己的脚本语言
-
作者:dong...
- 三年级语文上册 仿写句子 需要的直接下载打印吧
-
描写秋天的好句好段1.秋天来了,山野变成了美丽的图画。苹果露出红红的脸庞,梨树挂起金黄的灯笼,高粱举起了燃烧的火把。大雁在天空一会儿写“人”字,一会儿写“一”字。2.花园里,菊花争奇斗艳,红的似火,粉...
- C++|那些一看就很简洁、优雅、经典的小代码段
-
目录0等概率随机洗牌:1大小写转换2字符串复制...
- 二年级上册语文必考句子仿写,家长打印,孩子照着练
-
二年级上册语文必考句子仿写,家长打印,孩子照着练。具体如下:...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- wireshark怎么抓包 (75)
- qt sleep (64)
- cs1.6指令代码大全 (55)
- factory-method (60)
- sqlite3_bind_blob (52)
- hibernate update (63)
- c++ base64 (70)
- nc 命令 (52)
- wm_close (51)
- epollin (51)
- sqlca.sqlcode (57)
- lua ipairs (60)
- tv_usec (64)
- 命令行进入文件夹 (53)
- postgresql array (57)
- statfs函数 (57)
- .project文件 (54)
- lua require (56)
- for_each (67)
- c#工厂模式 (57)
- wxsqlite3 (66)
- dmesg -c (58)
- fopen参数 (53)
- tar -zxvf -c (55)
- 速递查询 (52)