一、Socket简介

  1.Socket概述

  Java最初是作为网络编程语言出现的,它对网络的高度支持,使得客户端和服务器流畅的沟通变成现实。而在网络编程中,使用最多的就是Socket,每一个实用的网络程序都少不了它的身影。

  在计算机网络编程技术中,两个进程,或者说两台计算机可以通过一个网络通信实现数据的交换,这种通信链路的端点就被称为“套接字”(即Socket),Socket是网络驱动层提供给应用程序的接口或者说是一种机制。举一个物流快递的例子来说明Socket,发件人将写有收货地址信息的货物送到快递站,发件人不用关心物流是如何进行的,货物被送到收货人所在地区的快递点,进行配送,收货人等待收货就可以了,这个过程很形象地说明了信息在网络中传递的过程。其中货物就是信息,2个快递点就是2个端点Socket。信息在网络中寻址传递,应用程序并不关心,只负责准备发送数据和接收数据。

  

  2.Socket通信原理

  对于编程人员来说,无需了解Socket底层机制是如何传送数据的,而是直接将数据提交给Socket,Socket会根据应用程序提供相关的信息。通过一系列计算,绑定IP及信息数据,将数据交给驱动程序向网络上发送出去。

  Socket的底层机制非常复杂,Java平台提供了一些虽然简单但相当强大的类,可以简单地有效地使用Socket开发通信程序而无需了解底层机制。

  

  3. java.net包

  java.net包提供了若干支持基于套接字的客户端/服务器通信的类。

  java.net包中常用的类有Socket、ServerSocket、DatagramPacket、DatagramSocket、InetAddress、URL、URLConnection和URLEncoder等。

  为了监听客户端的连接请求,可以使用ServerSocket类。Socket类实现用于网络上进程通信的套接字。DtatagramSocket类使用UDP协议实现客户端和服务器套接字。DatagramPacket类使用DatagramSocket类的对象封装设置和收到的数据报。InetAddress类表示Internet地址。在创建数据报文和Socket对象时,可以使用InetAdress类

二、基于TCP协议的Socket编程

  java.net包的两个类Socket和ServerSocket,分贝用来实现双向安全连接的客户端和服务器端,他们是基于TCP协议进行工作的,它的工作过程如同打电话的过程,只有双方都接通了,才能开始通话。

  进行通信时,Socket需要借助数据流来完成数据的传递工作。如果一个应用程序要通过网络向另一个应用程序发送数据,只要简单的创建Socket,然后将数据写入到与该Socket关联的输出流即可。对应的,接受方的应用程序创建Socket,从相关的输入流读取数据即可。

  

  1. Socket类

  Socket类在客户端和服务器端之间建立连接。可用Socket类的构造方法创建套接字,并将此套接字连接至制定的主机和端口。

  • 构造方法

    • Socket s = new Socket(hostName,port);  hostName:主机名,port:端口号。创建对象时可能会抛出UnknownHostException或IOException异常,必须捕获它们。
    • Socket s = new Socket(address,port);   address:InetAddress对象。创建对象时可能会抛出UnknownHostException或IOException异常,必须捕获它们。
  • 常用方法
方法名 说明
InetAddress getInetAddress() 返回与Socket对象关联的InetAddress
int getPort() 返回此Socket对象所连接的远程端口
int getLocalPort 返回此Socket对象所连接的本地端口
InputStream getInputStream() 返回与此套接字关联的InputStream
OutputStream getOutputStream() 返回与此套接字关联的OutputStream
void  close() 关闭该Socket

  2.ServerSocket类

  ServerSocket对象等待客户端建立连接,连接建立后进行通信。

  • 构造方法

    • ServerSocket s = new ServerSocket(port);  port:端口号。创建对象时可能会抛出IOException异常,必须捕获它们。
    • ServerSocket s = new ServerSocket(port,maxqu);   maxqu:最大队列长度队列长度表示系统在拒绝连接前可以拥有的最大客户端连接数。
  • 常用方法

  Socket类中列出的方法同样适用于ServerSocket,此外,ServerSocket类具有accept()方法,此方法用于等待客户端发起通信,这样Sockt对象就可以用于进一步的数据传输

  下面是一个简单例子:

package cn.yu.SocketDemo;
import java.io.*;
import java.net.*;
public class TCPSever {
/**
   * 服务器端
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
// TODO Auto-generated method stub
//使用ServerSocket
ServerSocket server = new ServerSocket(); //每个用户在程序中就是一个Socket
Socket client = null; //等待客户端连接
client = server.accept(); //像客户端打印信息
PrintWriter out = null; //准被向客户端打印信息
out = new PrintWriter(client.getOutputStream());
out.println("我是服务器端,Hello World");
//关闭流和Socket对象
out.close();
client.close();
server.close();
}
}
package cn.yu.SocketDemo;
import java.io.*;
import java.net.*;
public class TCPClient {
/**
   * 客户端
* @param args
* @throws IOException
* @throws UnknownHostException
*/
public static void main(String[] args) throws UnknownHostException, IOException {
// TODO Auto-generated method stub
//表示一个客户端的Socket
Socket client = null; //表示一个客户端的输入信息
BufferedReader buf = null;
client = new Socket("localhost",8000);
buf = new BufferedReader(new InputStreamReader(client.getInputStream()));
System.out.println(buf.readLine());
//关闭流和Socket对象
buf.close();
client.close();
}
}  

  

  注意,先执行服务器端代码,在执行客户端代码。

三、使用Socket编程实现用户登录

  1.实现单用户登陆  

  Socket编程一般分为4个步骤:

  • 建立连接
  • 打开Socket关联的输入/输出流
  • 从数据流中读写信息
  • 关闭所有的数据流和Socket
package cn.yu.SocketDemo;
import java.io.Serializable; /*
* 用户类:要传递的信息
*/
public class User implements Serializable {
private String loginName; //用户名
private String pwd; //用户密码
public User(String loginName,String pwd){
super();
this.loginName = loginName;
this.pwd = pwd;
}
//getter/setter方法
public String getLoginName() {
return loginName;
}
public void setLoginName(String loginName) {
this.loginName = loginName;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
}
package cn.yu.SocketDemo;
import java.io.*;
import java.net.*; public class TCPClient { /**
* 客户端
* @param args
* @throws IOException
* @throws UnknownHostException
*/
public static void main(String[] args) throws UnknownHostException, IOException {
// TODO Auto-generated method stub
//表示一个客户端的Socket,制定服务器的位置及端口
Socket client = new Socket("localhost",8000);; //打开输入/输出流
OutputStream os = client.getOutputStream();
InputStream is = client.getInputStream(); //对象序列化
ObjectOutputStream oos = new ObjectOutputStream(os); //发送客户端登陆信息,即向输出流中写入信息
User user = new User();
user.setLoginName("lipengfei");
user.setPwd("123456");
oos.writeObject(user);
client.shutdownOutput(); //表示一个客户端的输入信息
BufferedReader buf = new BufferedReader(new InputStreamReader(is));
String reply = null;
while((reply = buf.readLine())!=null){
System.out.println("我是客户端,服务器的响应是:"+reply);
}
buf.close();
oos.close();
is.close();
os.close();
client.close();
}
}
package cn.yu.SocketDemo;
import java.io.*;
import java.net.*;
public class TCPSever {
/**
* 服务器端
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException,ClassNotFoundException {
// TODO Auto-generated method stub
//表示一个客户端的Socket(ServerSocket),指定端口并开始监听
ServerSocket serverSocket = new ServerSocket(8000); //使用accept()方法等待客户端连接,每个用户在程序中就是一个Socket
Socket socket = serverSocket.accept(); //打开输入/输出流
InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream(); //反序列化
ObjectInputStream objectInputStream = new ObjectInputStream(inputStream); //从客户端获取信息,即从输入流读取信息
User user = (User)objectInputStream.readObject();
if(!(user==null)){
System.out.println("我是服务器,客户登录信息为:"+user.getLoginName()+","+user.getPwd());
} //给客户端一个响应,即向输出流中写入信息
String reply = "欢迎你"+user.getLoginName()+",登陆成功!";
outputStream.write(reply.getBytes()); //关闭资源
outputStream.close();
inputStream.close();
socket.close();
serverSocket.close();
} }

  

  这个例子采用一问一答的模式,先启动服务器进入监听状态,等待客户端的连接请求,连接成功后,客户端先“发言”,服务器给予“回应”

  2.实现多客户端用户登录

  一个服务器不可能只针对一个客户端服务,一般是面向很多的客户端同时提供服务,但是单线程实现必然是这样的结果。解决这个问题的办法是采用多线程的方式,可以在"服务器端"创建一个专门负责监听的应用主服务程序、一个专门负责响应的线程程序。这样就可以利用多线程处理多个请求。

  

package cn.yu.SocketDemo;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.OutputStream;
import java.net.Socket;
/*
* 线程类
*/
public class LoginThread extends Thread{
Socket socket = null;
//每启动一个线程,连接对应的Socket
public LoginThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try{
//打开输入/输出流
InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream(); //反序列化
ObjectInputStream objectInputStream = new ObjectInputStream(inputStream); //从客户端获取信息,即从输入流读取信息
User user = (User)objectInputStream.readObject();
if(!(user==null)){
System.out.println("我是服务器,客户登录信息为:"+user.getLoginName()+","+user.getPwd());
} //给客户端一个响应,即向输出流中写入信息
String reply = "欢迎你"+user.getLoginName()+",登陆成功!";
outputStream.write(reply.getBytes()); //关闭资源
outputStream.close();
inputStream.close();
socket.close();
//serverSocket.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
package cn.yu.SocketDemo;
import java.io.*;
import java.net.*;
public class TCPSever {
/**
* 服务器端
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException,ClassNotFoundException {
// TODO Auto-generated method stub
//表示一个客户端的Socket(ServerSocket),指定端口并开始监听
ServerSocket serverSocket = new ServerSocket(8000); //使用accept()方法等待客户端连接,每个用户在程序中就是一个Socket
Socket socket = null; //监听一直进行中
while(true){
socket = serverSocket.accept();
LoginThread loginThread = new LoginThread(socket);
loginThread.start();
}
}
}
package cn.yu.SocketDemo;
import java.io.*;
import java.net.*;
import java.util.*;
public class TCPClient {
/**
* 客户端
* @param args
* @throws IOException
* @throws UnknownHostException
*/
public static void main(String[] args) throws UnknownHostException, IOException {
// TODO Auto-generated method stub
for (int i = 0; i < 5; i++) {
//表示一个客户端的Socket,指定服务器的位置及端口
Socket client = new Socket("localhost",8000); //打开输入/输出流
OutputStream os = client.getOutputStream();
InputStream is = client.getInputStream(); //对象序列化
ObjectOutputStream oos = new ObjectOutputStream(os); //发送客户端登陆信息,即向输出流中写入信息
User user = new User();
user.setLoginName("lipengfei"+i);
String pwdRandom = "@yu"+(new Random().nextInt(100)+1);
user.setPwd(pwdRandom);
oos.writeObject(user);
client.shutdownOutput(); //接收服务器端的响应,即从输入流中读取数据
BufferedReader buf = new BufferedReader(new InputStreamReader(is));
String reply = null;
while((reply = buf.readLine())!=null){
System.out.println("我是客户端,服务器的响应是:"+reply);
}
//关闭资源
buf.close();
oos.close();
is.close();
os.close();
client.close();
}
}
}

  

  3.InetAddress类

  java.net包中的InetAddress类用于封装IP和DNS。要创建InetAddress类的实例,可以使用工厂方法,因为此类没有可用的构造方法

方法名 说明
static InetAddress getLocalHost() 返回表示本地主机的InetAddress对象
static InetAddress getByName(String hostName) 返回指定主机名为hostName的InetAddress对象
static InetAddress[]  getAllByName(String hostName) 返回指定主机名为hostName的所有可能的InetAddress对象组

  如果找不到主机,两种方法都将抛出UnknowHostNameException异常。

package cn.yu.SocketDemo;
import java.net.InetAddress;
import java.net.UnknownHostException;
/*
* 输出本地主机的地址
*/
public class InetAddressTest {
public static void main(String[] args) {
try{
InetAddress inetAddress = InetAddress.getLocalHost();
System.out.println("本地主机的地址是:"+inetAddress); //输出结果:域名/IP
}catch (UnknownHostException e){
e.printStackTrace();
}
}
}

  

四、基于UDP协议的Socket编程

  基于TCP协议的通信是安全的,是双向的,如同拨打10086服务电话,需要现有服务端,建立双向连接后,才开始数据通信,而UDP的网络通信就不一样了,只需要指明对方地址,然后将数据送出去,并不会事先连接。这样的网络通信是不安全的,所以应用在如聊天系统、咨询系统等场合下。

  先了解下"数据报",是表示通信的一种报文类型,使用数据报进行通信时无须事先建立连接,它是基于UDP协议进行的

  Java中有2个类可以使用数据报实现通信的类,即DatagramPacket和DatagramSocket类。DatagramPacket起到数据容器的作用,DatagramSocket用于发送或接收DataPacket

  DatagramPacket不提供发送或接收数据的方法,而DatagramSocket类提供send()和recieve()方法,用于通过套接字发送和接收数据报。

  1. DatagramPacket类

  • 构造方法

  客户端向外发送数据,必须首先创建一个DatagramPacket对象,在使用DatagramSocket对象发送。

构造方法 说明
DatagramPacket(byte[] data,int size) 构造DatagramPacket对象,封装长度为size的数据包
DatagramPacket(byte[] buf,int length,InetAddress address,int port) 构造DatagramPacket对象,封装长度为length的数据包并发送到指定的主机,端口号
  • 常用方法
方法 说明
byte[] getData() 返回字节数组,该数组包含接收到或要发送的数据报中的数据
int  getLength() 返回发送或接收到的数据的长度
InetAddress  getAddress() 返回发送或接收此数据报的主机的IP地址
int  getPort() 返回发送或接收此数据报的主机的端口号

  2. DatagramSocket类

  • 构造方法

  DatagramSocket类不维护连接状态,不产生输入/输出数据流,它的唯一作用就是接收和发送DatagramPacket对象封装好的数据报。

构造方法 说明
DatagramSocket() 创建一个DatagramSocket对象,并将其与本机地址上任何可用的端口绑定
DatagramSocket(int  port) 创建一个DatagramSocket对象,并将其与本机地址上指定的端口绑定
  • 常用方法
方法 说明
void  connect(InetAddress address,int  port) 将当前DatagramSocket对象连接到远程地址的指定端口
void close() 关闭当前DatagramSocket对象
void  disconnect() 断开当前DatagramSocket对象的连接
int  getLocalPort() 返回当前DatagramSocket对象绑定的本地主机的端口号
void  send(DatagramPacket p) 发送指定的数据报
void  receive(DatagramPacket  p) 接收数据报。收到数据报以后,存放在指定的DatagramPacket对象中

  3. 使用Socket编程实现客户咨询  

  利用UDP通信的两个端点是平等的,也就是说通信的两个程序关系是对等的,没有主次之分,甚至他们的代码都可以完全一样,这一点要与基于TCP协议的Socket程序区分开来。

  基于UDP协议的Socket网络编程一般可以分为4步:

  • 利用DatagramPacket对象封装数据包;
  • 利用Datagramsocket对象发送数据包;
  • 利用DatagramSocket对象接收数据包;
  • 利用DatagramPacket对象处理数据包;
package cn.yu.SocketDemo;
import java.io.IOException;
import java.net.*;
public class Send { public static void main(String[] args) {
InetAddress ia=null;
DatagramSocket ds=null;
try{
String mess="你好,我想咨询一个问题。";
//显示与本地对话框
System.out.println("我 说:"+mess); //获取本地主机地址
ia=InetAddress.getByName("localhost"); //创建DatagramPacket对象,封装数据
DatagramPacket dp=new DatagramPacket(mess.getBytes(),mess.getBytes().length ,ia,8800); //创建DatagramSocket对象,向服务器发送数据
ds=new DatagramSocket();
ds.send(dp); byte[] buf=new byte[1024];
DatagramPacket dpre=new DatagramPacket(buf,buf.length);
//创建DatagramSocket对象,接收数据
//ds=new DatagramSocket(8800);
ds.receive(dpre); //接收数据报,存放在指定的DatagramPacket对象中 //显示接收到的信息
String reply=new String(dpre.getData(),0,dpre.getLength());
System.out.println(dpre.getAddress().getHostAddress()+"说:"+reply); }catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally{
//关闭DatagramSocket对象
ds.close();
}
}
}
package cn.yu.SocketDemo;
import java.io.IOException;
import java.net.*;
public class Receive {
public static void main(String[] args) {
DatagramPacket dp=null;
DatagramSocket ds=null;
DatagramPacket dpto=null;
try{
//创建DatagramPacket对象,用来准备接收数据包
byte[] buf=new byte[1024];
dp=new DatagramPacket(buf,buf.length);
//创建DatagramSocket对象,接收数据
ds=new DatagramSocket(8800);
ds.receive(dp); //显示接收到的信息
String mess=new String(dp.getData(),0,dp.getLength());
System.out.println(dp.getAddress().getHostAddress()+"说:"+mess); String reply="你好,我在,请咨询!";
//显示与本地对话框
System.out.println("我 说:"+reply); //创建DatagramPacket对象,封装数据
SocketAddress sa=dp.getSocketAddress();
dpto=new DatagramPacket(reply.getBytes(),reply.getBytes().length ,sa);
ds.send(dpto); }catch (IOException e) {
e.printStackTrace();
}finally{
ds.close();
}
}
}

  

五、两种通信方式比较

 

  

Java高级特性 第9节 Socket机制的更多相关文章

  1. Java高级特性 第5节 序列化和、反射机制

    一.序列化 1.序列化概述 在实际开发中,经常需要将对象的信息保存到磁盘中便于检索,但通过前面输入输出流的方法逐一对对象的属性信息进行操作,很繁琐并容易出错,而序列化提供了轻松解决这个问题的快捷方法. ...

  2. Java高级特性 第6节 注解初识

    一.注解概述 Java注解也就是Annotation,是Java代码里的特殊标记,它为Java程序代码提供了一种形式化的方法,用来表达额外的某些信息,这些信息是代码本身无法表示的. 注解以标签的形式存 ...

  3. Java高级特性 第2节 java中常用的实用类(1)

    一.Java API Java API即Java应用程序编程接口,他是运行库的集合,预先定义了一些接口和类,程序员可以直接调用:此外也特指API的说明文档,也称帮助文档. Java中常用的包: jav ...

  4. Java高级特性 第14节 解析XML文档(2) - SAX 技术

    一.SAX解析XML文档 SAX的全称是Simple APIs for XML,也即XML简单应用程序接口.与DOM不同,SAX提供的访问模式是一种顺序模式,这是一种快速读写XML数据的方式.当使用S ...

  5. Java高级特性 第13节 解析XML文档(1) - DOM和XPath技术

    一.使用DOM解析XML文档 DOM的全称是Document Object Model,也即文档对象模型.在应用程序中,基于DOM的XML分析器将一个XML文档转换成一个对象模型的集合(通常称DOM树 ...

  6. Java高级特性 第11节 JUnit 3.x和JUnit 4.x测试框架

    一.软件测试 1.软件测试的概念及分类 软件测试是使用人工或者自动手段来运行或测试某个系统的过程,其目的在于检验它是否满足规定的需求或弄清预期结果与实际结果之间的差别.它是帮助识别开发完成(中间或最终 ...

  7. Java高级特性 第8节 网络编程技术

    一.网络概述 1.网络的概念和分类 计算机网络是通过传输介质.通信设施和网络通信协议,把分散在不同地点的计算机设备互连起来,实现资源共享和数据传输的系统.网络编程就就是编写程序使联网的两个(或多个)设 ...

  8. Java高级特性 第7节 多线程

    一.进程与线程的概念 1. 进程 进程是应用程序的执行实例,有独立的内存空间和系统资源. 如上图,标红色的是一个Office Word进程. 进程的特点: 动态性:进程是动态的创建和消亡: 并发性:操 ...

  9. Java高级特性 第1节 集合框架和泛型

    Java中,存储多个同类型的数据,可以用数组来实现,但数组有一些缺陷: 数组长度固定不变,布恩那个很好的适应元素数量动态变化的情况 可以通过数组.length获取数组长度,却无法直接获取数组中实际存储 ...

随机推荐

  1. LoadLibrary 失败的解决

    工作中遇到调用Loadlibrary 偶发失败的问题,不是必现,而且这种错误只是在程序初始化的时候出现,初始化成功后当然不会调用,而初始化也不是经常做的动作,所以查找原因起来比较麻烦,调试过程中发现有 ...

  2. shell练习题6

    需求如下: 有日志access.log,部分内容如下: 127.0.0.1 - - [03/Jul/2018:00:00:01 +0800] "GET / HTTP/1.1" 20 ...

  3. 自动化运维之Saltstack

    第三十八课 自动化运维之Saltstack 目录 一.自动化运维介绍 二. saltstack安装 三. 启动saltstack服务 四. saltstack配置认证 五. saltstack远程执行 ...

  4. 常用的数学函数-S

    // abs — 获取[数值]的绝对值 $; echo abs($int).'<br>'; $float=-2.34; echo abs($float).'<hr>'; //c ...

  5. python的标准数据类型

    python有5种标准的数据类型 1. number(数字) int(有符号的整形) long(长整[也可以代表八进制和16进制]) float(浮点型) complex(复数类型) 2.string ...

  6. Lavarel Route::resource

    RESTful Resource controller A RESTful resource controller sets up some default routes for you and ev ...

  7. Zynq系列程序逻辑固化方法

    1.创建一个BOOT镜像 该小节主要讲述zynq平台利用软件套件SDK创建一个可固化BOOT镜像. 1.1  选择Ad9361_Eque1工程,选择Xilinx Tools → Create Boot ...

  8. holer实现外网访问本地tomcat

    外网访问内网Tomcat 内网主机上安装了Tomcat,只能在局域网内访问,怎样从公网也能访问本地Tomcat? 本文将介绍使用holer实现的具体步骤. 1. 准备工作 1.1 安装Java 1.7 ...

  9. php lcg_value与mt_rand生成0~1随机小数的效果比较

    因工作需要使用PHP生成0~1随机小数,之前写过一篇<php生成0~1随机小数方法>,基于mt_rand()及mt_getrandmax()实现. 后来有网友评论,php原生方法lcg_v ...

  10. Python 列表(List)

    Python 列表(List) 序列是Python中最基本的数据结构.序列中的每个元素都分配一个数字 - 它的位置,或索引,第一个索引是0,第二个索引是1,依此类推. Python有6个序列的内置类型 ...