关于Java的网络编程,在现在流行的很多框架中都对它进行了封装,从而方便了我们进行编程,了解它能帮助我们更加容易的去阅读源码
TCP传播
服务端,我们采用的ServerSocket,它对外提供的构造方法如下:
- ServerSocket()throws IOException
- ServerSocket(int port)throws IOException
- ServerSocket(int port, int backlog)throws IOException
- ServerSocket(int port, int backlog, InetAddress bindAddr)throws IOException
其中,port很容易理解,表示要监听的端口号,backlog的解释如下:
@param backlog requested maximum length of the queue of incoming connections.
正在请求连接的队列的最大长度(好吧,我觉得我翻译的有点烂),意思如下:TCP协议中,客户端和服务端完全的连接需要经过三次握手,而还没经过三次连接的请求需要保存在一个请求队列里,此时,我们可以通过 backlog参数来设置队列的长度,没有指定时,默认为50。
参数bindAddr用于绑定服务器IP地址,当你的机器有多个网卡时,就可以拥有多个IP了,此时可以选择其中的一个进行绑定。
下面的例子是一个运用多线程,模拟浏览器向服务器请求html例子:[一下代码都可以在控制台同时运行,然后通过切换控制台看每个程序的信息]
服务端
其中,serverSocket.accept()就是一直等待客户端的请求连接,它在等到连接后,会返回一个Socket对象,这是我们的线程类HttpdThread的构造方法参数是Socket的原因。感兴趣的朋友可以通过如下方式,在run方法中打印请求和服务端的IP,端口信息:
- socket.getPort();//请求连接的服务器的端口
- socket.getLocalPort();//客户端发起请求的端口
- socket.getRemoteSocketAddress();//服务端地址
- socket.getLocalSocketAddress();//客户端地址
public class Httpd {
@SuppressWarnings("resource")
public static void main(String[] args) throws IOException {
//监听的端口
ServerSocket ss = new ServerSocket(8888);
System.out.println("HTTP 服务程序已就绪...");
while (true){
new HttpdThread(ss.accept()).start();
}
}
}
这里要说一下请求头,由于浏览各种各样,实现也有可能不同,有些浏览器不加请求头就可以直接打开了,而有一些却不行,我这里用的是火狐和谷歌,加了请求头后可以显示。
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.StringTokenizer;
public class HttpdThread extends Thread {
Socket socket;
HttpdThread(Socket s) {
socket = s;
}
@SuppressWarnings("resource")
public void run() {
try {
//请求头,告诉浏览器你请求的内容是什么,我这里请求的是html所以就设置html
socket.getOutputStream().
write(("HTTP/1.1 200 OK\r\n" + //响应头第一行
"Content-Type: text/html; charset=utf-8\r\n" + //简单放一个头部信息
"\r\n" //这个空行是来分隔请求头与请求体的
).getBytes("utf-8"));
// 打开与连接绑定的输入流和输出流
BufferedReader in = new BufferedReader(
new InputStreamReader(socket.getInputStream(), "GBK"));
OutputStream out = socket.getOutputStream();
// 读取客户请求
String request = in.readLine();
System.out.println("收到请求:" + request);
// 根据HTTP 协议分析客户请求内容(只处理GET 命令)
StringTokenizer st = new StringTokenizer(request);
if ((st.countTokens() >= 2) && st.nextToken().equals("GET")) {
// 从请求中取出文档的文件名,支持默认索引文件
String filename = st.nextToken();
if (filename.startsWith("/"))
filename = filename.substring(1);
if (filename.endsWith("/"))
filename += "index.html";
if (filename.equals(""))
filename += "index.html";
try {
// 读取文件中的内容并写到socket 的输出流
InputStream file = new FileInputStream(filename);
byte[] data = new byte[file.available()];
file.read(data);
out.write(data);
out.flush();
} catch (FileNotFoundException exc) {
PrintWriter pout = new PrintWriter(
new OutputStreamWriter(out, "GBK"), true);
pout.println("错误代码404:未发现目标!");
}
} else {
PrintWriter pout = new PrintWriter(
new OutputStreamWriter(out, "GBK"), true);
pout.println("错误代码400:错误的请求!");
}
// 关闭连接
socket.close();
} catch (IOException exc) {
System.out.println("I/O错误:" + exc);
}
}
}
其中,我们html文件放在项目的目录下:
运用线程池进行改进,这里我用的线程池是ThreadPoolExecutor:
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import com.fang.servlet.HttpdThread;
public class HttpdExecutor{
//设置线程池有3个核心线程,线程池最大有6个线程,线程等待的时间为200毫秒,
//时间单位为毫秒,线程的等待队列长度是2
private ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3,6,200,TimeUnit.MILLISECONDS
,new ArrayBlockingQueue<Runnable>(2));
public HttpdExecutor() {}
public void addThread(HttpdThread httpdThread){
if(threadPoolExecutor.getQueue().size() == 2){
System.out.println("目前线程池已满,请稍后");
return;
}
threadPoolExecutor.submit(httpdThread);
}
}
主线程改变while(true)部分:
while(true){
HttpdThread httpdThread = null;
try {
httpdThread = new HttpdThread(serverSocket.accept());
} catch (IOException e) {
System.out.println("系统错误");
}
//添加线程
executor.addThread(httpdThread);
try {
buff = new BufferedWriter(new OutputStreamWriter(
new FileOutputStream("message.txt",true)));
buff.write(httpdThread.toString()+"\r\n");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
buff.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
UDP传播
UDP不同于TCP,它是不可靠的,不需要具体知道传送的主机地址,而且它是以数据包的形式进行发送,读取,而不是TCP中流形式。涉及的类如下:
- DatagramPacket:将数据字节填充到UDP包中,这称为数据报
- DatagramSocket:来发送这个数据包。要接受数据,可以从DatagramSocket中接受一个 Datagra mPack对象,然后从该包中读取数据的内容。
下面的例子用于模拟客户端进行UDP广播,服务端进行接收,服务端以applet形式呈现:
public class ClientDemo extends Thread{
private String weather = "已出牌:请收听";
private int port = 8888;
InetAddress address = null;
MulticastSocket socket = null;
public ClientDemo() {
super();
}
public void init(){
try {
//广播地址
address = InetAddress.getByName("224.0.0.1");
System.out.println(address);
//实例化多点广播套接字
socket = new MulticastSocket(port);
//指定发送范围是本地网络
socket.setTimeToLive(1);
//加入广播组
socket.joinGroup(address);
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run(){
while(true){
DatagramPacket packet = null;
byte[] buff = weather.getBytes();
packet = new DatagramPacket(buff, buff.length,address,port);
System.out.println(new String(buff));
try {
socket.send(packet);
sleep(3000);
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
ClientDemo client = new ClientDemo();
client.init();
client.start();
}
}
服务端
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextArea;
public class Server extends JFrame implements Runnable, ActionListener {
private static final long serialVersionUID = 1L;
// UDP实现广播数据报
// 加入同一个组中的主机随时都可以接收到信息
private static int port;
private static InetAddress group = null;
private JButton button_begin;
private JButton button_stop;
private JTextArea textArea_1;
private JTextArea textArea_2;
private Thread thread;
private MulticastSocket socket = null;
private boolean flag = false;
public Server(){
setTitle("UDP实现广播出牌指令");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(100,50,360,380);
JPanel panel = new JPanel(new FlowLayout());
button_begin = new JButton("开始接收");
panel.add(button_begin);
button_begin.addActionListener(this);
button_stop = new JButton("停止接收");
panel.add(button_stop);
button_stop.addActionListener(this);
getContentPane().add(panel,BorderLayout.NORTH);
JPanel panel_1 = new JPanel(new GridLayout(1, 2));
textArea_1 = new JTextArea();
textArea_1.setLineWrap(true);
panel_1.add(textArea_1);
textArea_2 = new JTextArea();
textArea_2.setLineWrap(true);
panel_1.add(textArea_2);
getContentPane().add(panel_1,BorderLayout.CENTER);
thread = new Thread(this);
validate();//刷新
port = 8888;
try {
//服务端也要加入到同一网络中
group = InetAddress.getByName("224.0.0.1");
socket = new MulticastSocket(port);
socket.joinGroup(group);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Server server = new Server();
server.setVisible(true);
}
@Override
public void actionPerformed(ActionEvent e) {
if (e.getSource() == button_begin) {
if (!thread.isAlive()) {
thread = new Thread(this);
}
thread.start();
flag = false;
}
if (e.getSource() == button_stop) {
flag = true;
}
}
@Override
public void run() {
while (true) {
byte[] buff = new byte[1024];
DatagramPacket packet = null;
//创建数据包的对象,指明大小
packet = new DatagramPacket(buff, 0, buff.length);
try {
socket.receive(packet);
//接收数据包后,可以用数据包对象get数据
String message = new String(packet.getData(), 0, packet.getLength());
textArea_1.setText("正在接收的内容:\n" + message);
textArea_2.append(message + "\n");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if (flag) {
break;
}
}
}
}