[黑马程序员]关于网络的笔记
Socket套接字,封装了底层数据传输的细节,程序通过它与服务器建立连接,实现通信。在客户/服务器通信模式中,客户端主动建立与服务器的连接(需要创建Socket),服务器收到连接请求后也创建与客户连接的Socket。Socket可以看做通信两端的收发器,客户端和服务器都通过它进行数据传输。
附注:
UDP协议:用户数据报协议,面向非连接,不可靠传输,传输速度快,适用于传输少量数据
TCP协议:传输控制协议,面向连接,可靠传输,传输速度慢,适用于传输大量数据
Java中有种套接字:
java.net.Socket、java.net.ServerSocket、java.net.DatagramSocket,前面两个建立在TCP协议上的,后面一个建立在UDP协议上的。
java.net.Socket、java.net.ServerSocket是建立在TCP协议的基础上,TCP以恶意是一种可靠的数据传输协议,如果数据传输中途丢失或者损坏,TCP会再次发送数据;如果数据到达到接受方时顺寻打乱,那么TCP协议在接收方会重新恢复数据的正确顺序。但是传输速率会降低。
java.net.DatagramSocket建立在UDP协议上,它的传输速率较高,但是UDP协议,但是不可靠,他无法保证数据一定到达接收方,也不能保证到达的数据是正确的数据。它适合传输少量数据。
使用UDP协议通信,客户机和服务器是对等的,都是使用DatagramSocket,它既可以接受数据,也可以传输数据。UDP是无连接的,所以不存在一一对应关系。每个DatagramSocket与本地地址(ip+端口)绑定。DatagramSocket可以把数据把数据发送给任意一个远程DatagramSocket,也可以接受任意一个远程的DatagramSocket的数据。接受和发送的数据封装在DatagramPacket中。
一、 DatagramPacket类
它可以分两类,一个是用于发送,一个是用来接收。用于发送数据,构造方法要指定目的地址(DatagramPacket包含:将要发送的数据、其长度、远程主机的 IP 地址和远程主机的端口号),而接受方无需指定。
发送方:
1、DatagramPacket(byte[] buf, int length, InetAddress address, int port)
2、DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port)
3、DatagramPacket(byte[] buf, int offset, int length, SocketAddress address)
4、DatagramPacket(byte[] buf, int length, SocketAddress address)
发送方将buf中的数据发送出去。
接受方:
DatagramPacket(byte[] buf, int length)
DatagramPacket(byte[] buf, int offset, int length)
接收方把数据放到buf数组中。
如发送方代码可以像这样(使用InetAddress):
InetAddress ip = InetAddress.getByName("localhost");//getByName(远程主机名字)得到远程主机是InetAddress
int port = 8000;//指定远程主机监听的端口号
byte[] data = "message".getBytes();//要发送的数据,byte数组的形式
DatagramPacket dp = new DatagramPacket(byte[] data, data.length,ip,port);
或者使用SocketAddress:
InetAddress ip = InetAddress.getByName("localhost");//getByName(远程主机名字)得到远程主机是InetAddress
int port = 8000;//指定远程主机监听的端口号
SocketAddress remoteAddr = new SocketAddress(ip,port);
byte[] data = "message".getBytes();//要发送的数据,byte数组的形式
DatagramPacket dp = new DatagramPacket(byte[] data, data.length,remoteAddr);
可以通过DatagramPacket得到远程主机的地址:
public int getPort()
public InetAddress getAddress()
public SocketAddress getSocketAddress()
也可以得到接收到的数据以及数据长度
byte[] getData()
int getLength(),它并不一定等于buf数组的长度。
二、DatagramSocket类:
构造方法
DatagramSocket:每个DatagramSocket与本地地址,构造方法
DatagramSocket()由操作系统分配一个可用端口
DatagramSocket(int port)绑定端口
DatagramSocket(int port,InetAdress laddr)绑定端口且指定地址(适合一个主机有多个地址的情况)
DatagramSocket(SocketAdress addr)绑定地址(适合一个主机有多个地址的情况)
DatagramSocket获得绑定的地址:
int getLocalPort()
InetAddress getLocalAddress();
SocketAdress getLocalSocketAdress();
注意:InetAddress通常为IP地址;SocketAddress通常为 IP 地址 + 端口号。获得远程的地址是通过DatagramPacket获得
public int getPort()
public InetAddress getAddress()
public SocketAddress getSocketAddress()
发送和接受数据
public void send(DatagramPacket dp)
发送dp中的数据。注意atagramPacket需要包含:将要发送的数据、其长度、远程主机的 IP 地址和远程主机的端口号。由于UDP传输不可靠,所以调用send()方法数据发送正确但是并没有到达接方,也不会抛出异常。
public void receive(DatagramPacket dp)
接收数据,存放到dp中。如果网络上没有数据,那么执行方法的线程会阻塞,直到收到数据。如果接收到的数据长度超过缓冲区,那么超出的数据就会丢失,所以dp的缓冲区应该足够大(即buf数组足够大)。
使用UDP协议的聊天程序的例子:
package org.wang;
import java.awt.*;
import java.io.*;
import java.net.*;
public class Mychat extends Frame{
List list = new List(8);
TextField tfIP = new TextField(15);
TextField tfData = new TextField(25);
Panel panel = new Panel();
DatagramSocket ds ;
public Mychat(){
this.add(list);
this.add(panel,"South");
panel.setLayout(new BorderLayout());
panel.add(tfIP,"West");
panel.add(tfData,"East");
try {
ds = new DatagramSocket(3000);
} catch (SocketException e1) {
e1.printStackTrace();
}
new Thread(new Runnable(){
@Override
public void run() {
byte[] buf = new byte[1024];
DatagramPacket dp = new DatagramPacket(buf,buf.length);
while(true){
try {
ds.receive(dp);
} catch (IOException e) {
if(!ds.isClosed())
e.printStackTrace();
}
//new String(dp.getData(),0,dp.getLength())
//dp.getData().toString()
list.add(new String(dp.getData(),0,dp.getLength())+" FROM:"+
dp.getAddress().getHostAddress()+":"+
dp.getPort(),0);
}
}
}).start();
tfData.addActionListener(new ActionListener(){
@Override
public void actionPerformed(ActionEvent e) {
//获得对方IP地址
byte[] buf = tfData.getText().getBytes();
try {
DatagramPacket dp = new DatagramPacket(buf, buf.length,
InetAddress.getByName(tfIP.getText()),3000);
ds.send(dp);//这两句话都有异常
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
//发送消息后,清空文本框
tfData.setText("");
}
});
addWindowListener(new WindowAdapter(){
public void windowClosing(WindowEvent e) {
ds.close();
dispose();
System.exit(0);
}
}
);
}
public static void main(String[] args){
Mychat mychat = new Mychat();
mychat.setSize(400,580);
mychat.setTitle("chat");
mychat.setVisible(true);
//mychat.setResizable(false);
}
}
三、基于TCP协议的通信
客户端使用Socket与服务器通信,
Socket的构造方法较多,常用的有
Socket()通过系统默认类型的 SocketImpl 创建未连接套接字
Socket(String host, int port)
Socket(InetAddress address, int port)
其中host指定服务器的主机名,address指定服务器的地址、 port指定服务器的端口
客户端使用Socket请求与服务器建立连接,默认会一直等待下去,可以设置等待时间,void connect(SocketAddress endpoint, int timeout)
这时使用第一个构造方法:
Socket socket = new Socket();
SocketAddress addr = new InetSocketAddress(String hostname, int port)
//InetSocketAddress是SocketAddress是子类
socket.connect(addr,100000);
//connect(SocketAddress endpoint, int timeout)
在通信结束后应该及时关闭socket,如果在通信的过程中,如果发送方没有关闭socket就终止了程序,接收方就会抛出异常。
服务器端使用ServerSocket,常用的构造方法有:
ServerSocket(int port)
ServerSocket(int port, int backlog)
其中port为服务器绑定的端口,backlog表示接受的客户请求长度。
获得ServerSocket对象之后,程序调用accept()方法,监听绑定的端口,等待客户连接,如果收到一个连接请求,就返回一Socket对象,此Socket对象与客户端的Socket对象没什么区别
Socket socket = new ServerSocket(port).accept();
Socket和ServerSocket都提供了获得本地地址和端口的方法。另外,Socket提供了getInputStream()和getOutputStream()方法,分别获得InputStream对象和OutputStream对象,通过它们进行数据传输。
InputStream和OutputStream传输效率不是很高,一般情况下,通过过滤流来装饰:
对于OutputStream的装饰:
OutputStream out = socket.getOutputStream();
PrintWriter pw = new PrintWriter(out,true);//true表示自动刷新
对于InputStream的装饰:
InputStream in = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(in));
服务器端(下面的例子的service2()方法可以接受多个客户连接,且互不干扰):
package org.wang;
import java.io.*;
import java.net.*;
public class TcpServer {
ServerSocket server = null ;
Socket socket = null ;
void service(){//service()方法适用于单个客户程序连接
try {
server = new ServerSocket(1051);
socket = server.accept();
InputStream in = socket.getInputStream();
OutputStream out = socket.getOutputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
PrintWriter writer = new PrintWriter(out,true);
writer.println("欢迎你");
String msg ;
while(( msg = reader.readLine()) != null){
writer.println("回显:"+msg);
System.out.println(msg);
}
} catch (IOException e) {
e.printStackTrace();
}finally{
try {
if(socket!=null)socket.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
void service2(){//service2()方法适用于多个客户程序连接
try {
server = new ServerSocket(7000);
while(true){
socket = server.accept();//不能方法while的上面
new Thread(new Server(socket)).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
//new TcpServer().service();//service()只能接受单个客户
new TcpServer().service2();//接受多个可会连接
}
}
class Server implements Runnable{
Socket socket = null;
public Server(Socket socket){
this.socket = socket;
}
BufferedReader getReader(Socket socket){
InputStream in = null;
try {
in = socket.getInputStream();
} catch (IOException e) {
e.printStackTrace();
}
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
return reader ;
}
PrintWriter getWriter(Socket socket){
OutputStream out = null;
try {
out = socket.getOutputStream();
} catch (IOException e) {
e.printStackTrace();
}
PrintWriter writer = new PrintWriter(out,true);
return writer ;
}
@Override
public void run() {
BufferedReader reader = getReader(socket);
PrintWriter writer = getWriter(socket);
writer.println("欢迎你");
try {
String msg = null;
while(!((msg=reader.readLine()).equalsIgnoreCase("exit"))&&
(!msg.equalsIgnoreCase("quit"))){
String reverseMsg = (new StringBuffer(msg).reverse()).toString();
System.out.println(msg);
writer.println(msg+"-->"+reverseMsg);
}
} catch (IOException e) {
e.printStackTrace();
}finally{
try {
if(socket!=null)socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
客户端:
package org.wang;
import java.io.*;
import java.net.*;
public class TcpClient {
private String hostname="localhost";
private int port = 7000;
private Socket socket = null ;
public TcpClient(){
try {
socket = new Socket(hostname,port);
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
BufferedReader getReader(Socket socket){
InputStream in = null;
try {
in = socket.getInputStream();
} catch (IOException e) {
e.printStackTrace();
}
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
return reader ;
}
PrintWriter getWriter(Socket socket){
OutputStream out = null;
try {
out = socket.getOutputStream();
} catch (IOException e) {
e.printStackTrace();
}
PrintWriter writer = new PrintWriter(out,true);
return writer ;
}
private void connect(){
BufferedReader reader = getReader(socket);
PrintWriter writer = getWriter(socket);
BufferedReader sysReader = new BufferedReader(new InputStreamReader(System.in));
String msg = null;
try {
System.out.println(reader.readLine());
while((msg=sysReader.readLine())!=null){
writer.println(msg);
System.out.println(reader.readLine());
if(msg.equalsIgnoreCase("quit")||msg.equalsIgnoreCase("exit")){
break;
}
}
} catch (IOException e) {
e.printStackTrace();
}finally{
try {
if(socket!=null)socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
new TcpClient().connect();
}
}