Socket的英文原义是“孔”或者“插座”,中文是“套接字”。应用程序通常通过套接字向网络发出请求或者应答网络请求。他最早出现在UNIX系统中,是UNIX上的一套网络程序通讯的标准,已被广泛移植到其他平台。
在Internet上的主机一般运行了多个服务软件,同时提供了几种服务,每种服务都打开一个Socket并绑定到一个端口上,不同的端口对应于不同的服务进程。
Socket实质上提供了进程通信的端点,网络上的两个程序通过一个双向的通讯链路实现数据的交换,这个双向链路的一端称为一个Socket。TCP/IP的5层模型中,Socket位于应用层和传输层。
Socket分为三种类型:1、流式套接字SOCK_STREAM。提供了一个面向链接,可靠的数据传输服务,数据无差错、无重复的发送,且按发送顺序接收。内设流量控制,避免数据流超限。其实他对应使用的是TCP协议。2、数据报式套接字SOCK_DGRAM。提供无连接服务。数据报以独立包形式被发送,不提供无差错保证,数据可能丢失或重复,并且接收顺序无序。其实他对应使用的是UDP协议。3、原始式套接字SOCK_RAW。该接口允许对较低层次协议,如IP直接访问。可以接收本机网卡上的数据帧或数据包,对监听网络流量和分析很有作用。
java将每一种套接字封装到了不同的类中,这些类位于java.net包中。
流式套接字类:Socket类和ServerSocket类。
Socket类就是负责处理客户端通信的一个java类。两种常用的构造方法与使用说明:1、Socket(String host,int port)这个构造方法是创建一个流式套接字,并且将其连接到指定主机的指定端口号,即向host主机的port端口发起连接请求。2、Socket(String host,int port,InetAddresslocalAddr,int LocalPort)这个构造方法也是创建一个流式套接字,并将其连接到指定远程主机上的指定远程端口,向host主机的port端口发起连接请求,发起请求的计算机为localAddr,端口为localPort。其中InetAddress类表示互联网协议地址,包含IP地址,也就是java对IP地址的封装。
处理服务器端通信的ServerSocket类。两种常用的构造方法:1、ServerSocket()创建一个ServerSocket对象。2、ServerSocket(int port)创建一个ServerSocket对象,并绑定到指定的端口。
Socket变成步骤:服务器端:1、建立一个服务器Socket绑定指定端口并开始监听。2、使用accept()方法阻塞等待监听,获得新的连接。3、建立输入和输出流。4、在已有的协议上产生会话。5、使用close()关闭流和Socket。
客户端:1、建立客户端Socket连接,指定服务器的位置以及端口。2、得到Socket的读写流。3、利用流按照一定的协议对Socket进行读写操作。4、使用close()关闭流和Socket。
例如,使用Socket实现客户端和服务器端数据传输
服务器端
public class Server {
public static void main(String[] args) {
try {
ServerSocket ss = new ServerSocket(8800);
Socket s = ss.accept();
InputStream is = s.getInputStream();
OutputStream os = s.getOutputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
PrintWriter pw = new PrintWriter(os);
String info = null;
while((info=br.readLine())!=null)
System.out.println("服务器端接受信息:"+info);
String reply = "welcome!";
pw.write(reply);
pw.flush();
//关闭资源
pw.close();
br.close();
os.close();
is.close();
s.close();
ss.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
客户端
public class Client {
public static void main(String[] args) {
try {
Socket s = new Socket("localhost", 8800);
InputStream is = s.getInputStream();
OutputStream os = s.getOutputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
PrintWriter pw = new PrintWriter(os);
String info = "这是客户端发送信息!";
pw.write(info);
pw.flush();
s.shutdownOutput();
String reply = null;
while((reply=br.readLine())!=null)
System.out.println("客户端响应信息为:"+reply);
pw.close();
br.close();
os.close();
is.close();
s.close();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
运行结果:
在服务器控制台界面:
服务器端接受信息:这是客户端发送信息!
在客户端控制台界面:
客户端响应信息为:welcome!
使用对象序列化流对Socket发送和接收的数据进行封装,实现了网络中传输对象。因此,通过Socket传递java对象,采用的方法就是对象序列化。
在客户端通过ObjectOutputStream来写入java对象,而服务器端通过ObjectInputStream对以前使用ObjectOutputStream写入的对象,进行反序列化来读取或者重构对象。
注意:对象需要实现Serializable接口。
例如:
实体类:
public class User implements Serializable{
private String loginName;
private int password;
public User(String name,int pwd){
loginName = name;
password = pwd;
}
public String getLoginName() {
return loginName;
}
public void setLoginName(String loginName) {
this.loginName = loginName;
}
public int getPassword() {
return password;
}
public void setPassword(int password) {
this.password = password;
}
}
服务器端
public class ObjServer {
public static void main(String[] args) {
try {
ServerSocket ss = new ServerSocket(8900);
Socket s = ss.accept();
InputStream is = s.getInputStream();
ObjectInputStream ois = new ObjectInputStream(is);
OutputStream os = s.getOutputStream();
PrintWriter pw = new PrintWriter(os);
User user = (User)ois.readObject();
System.out.println("用户名:"+user.getLoginName()+" 用户密码:"+user.getPassword());
System.out.println("登录成功!");
pw.write("恭喜成功登录!");
pw.flush();
pw.close();
os.close();
ois.close();
is.close();
s.close();
ss.close();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
客户端
public class ObjClient {
public static void main(String[] args) {
try {
Socket s = new Socket("localhost", 8900);
OutputStream os = s.getOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(os);
InputStream is = s.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
User user = new User("小明",1234);
oos.writeObject(user);
String reply = null;
while((reply = br.readLine())!=null)
System.out.println("客户端响应:"+reply);
br.close();
is.close();
oos.close();
os.close();
s.close();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
为了实现在服务器方给多个客户提供服务的功能,可以使用多线程实现多客户的机制。服务器总是在指定的端口监听是否有客户请求,一旦监听到客户的请求,服务器就会启动一个专门的线程来响应客户的请求,而服务器在启动完线程后马上进入到监听状态,等待下一个客户的到来。
使用多线程时,实体类和客户端不用做任何改变,只需要将服务器端的任务以线程的方式单独处理
首先看服务器线程处理类:
public class ServerThread extends Thread{
Socket s = null;
public ServerThread(Socket s){
this.s = s;
}
@Override
public void run() {
try {
InputStream is = s.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
OutputStream os = s.getOutputStream();
PrintWriter pw = new PrintWriter(os);
String info = null;
while((info = br.readLine())!=null)
System.out.println("服务器接收到信息:"+info);
String reply = "这是服务器端发送信息!";
pw.write(reply);
pw.close();
os.close();
br.close();
is.close();
s.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
然后看服务器类
public class Server {
public static void main(String[] args) {
try {
ServerSocket ss = new ServerSocket(8800);
Socket s = null;
int num = 0;//记录当前客户端数量
while(true){
s = ss.accept();
ServerThread st = new ServerThread(s);
st.start();
num++;
System.out.println("当前有"+num+"个客户登录!");
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
当启动服务器后运行两次客户端程序,则在服务器端控制台输出:
当前有1个客户登录!
服务器接收到信息:这是客户端发送信息!
当前有2个客户登录!
服务器接收到信息:这是客户端发送信息!
当数据需要可靠传递时,我们可以采用流式套接字,比如:远程登录、文件传输等。
UDP协议:数据不可靠传输、代价小、效率高。
基于UDP协议的Socket(数据报十套接字):1、基于UDP协议。2、无连接。3、投出数据包快速高效。4、数据安全性不佳。比如:网络游戏、视频会议、QQ聊天。
java.net包提供了两个类来支持基于UDP协议的Socket编程:DatagramPacket类(数据包。封装了数据、数据长度、目标地址和目标端口)以及DatagramSocket类(发送和接收数据包的套接字,不维护连接状态,不产生输入输出流)。
注意:客户端要向外发送数据、必须首先创建一个DatagramPacket对象,在使用DatagramSocket对象发送。
基于UDP协议的Socket连接示例:
public class Server {
public static void main(String[] args) {
try {
//1.创建接受方(服务器)套接字,并绑定端口号
DatagramSocket ds=new DatagramSocket(8800);
//2.确定数据包接受的数据的数组大小
byte[] buf=new byte[1024];
//3.创建接受类型的数据包,数据将存储在数组中
DatagramPacket dp=new DatagramPacket(buf, buf.length);
//4.通过套接字接受数据
ds.receive(dp);
//5.解析发送方发送的数据
String mess=new String(buf,0,dp.getLength());
System.out.println("客户端说:"+mess);
//响应客户端
String reply="您好,我在的,请咨询!";
byte[] replys=reply.getBytes();
//响应地址
SocketAddress sa=dp.getSocketAddress();
//数据包
DatagramPacket dp2=new DatagramPacket(replys, replys.length,sa);
//发送
ds.send(dp2);
//6.释放资源
ds.close();
} catch (SocketException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public class Client {
public static void main(String[] args) {
//1.确定发送给服务器的信息、服务器地址以及端口
String mess="你好,我想咨询一个问题!";
byte[] buf=mess.getBytes();
InetAddress ia=null;
try {
ia=InetAddress.getByName("localhost");
} catch (UnknownHostException e) {
e.printStackTrace();
}
int port=8800;
//2.创建数据包,发送指定长度的信息到指定服务器的指定端口
DatagramPacket dp=new DatagramPacket(buf, buf.length,ia,port);
//3.创建DatagramSocket对象
DatagramSocket ds=null;
try {
ds=new DatagramSocket();
} catch (SocketException e) {
e.printStackTrace();
}
//4.向服务器发送数据包
try {
ds.send(dp);
} catch (IOException e) {
e.printStackTrace();
}
//接受服务器的响应并打印
byte[] buf2=new byte[1024];
//创建接受类型的数据包,数据将存储在数组中
DatagramPacket dp2=new DatagramPacket(buf2, buf2.length);
//通过套接字接受数据
try {
ds.receive(dp2);
} catch (IOException e) {
e.printStackTrace();
}
//解析服务器的响应
String reply=new String(buf2,0,dp2.getLength());
System.out.println("服务器的响应为:"+reply);
//5.释放资源
ds.close();
}
}