标签:
这篇博客是本人学习《Java网络程序设计》书中第4章套接字的学习总结。初学者网友学习这篇Java套接字文章,如果难于理解文章前面理论部分,可以先运行后面的程序,边看运行后面的程序边理解前面的原理,这对初学者是最好的方法。所有源代码都在文章后面我的github链接代码中。
——惠州学院 13网络工程 吴成兵 20160607
套接字(Socket)是由加利福尼亚大学伯克利分校首创的(University of California, Berkeley),它允许程序把网络连接看成一个流(Stream),可以向这个流写入字节,也可以从这个流读取字节,也叫流套接字。
Socket的英文原义是“孔”或“插座”。作为BSD UNIX的进程通信机制,取后一种意思。通常也称作”套接字”,用于描述IP地址和端口,是一个通信链的句柄,可以用来实现不同虚拟机或不同计算机之间的通信。在Internet上的主机一般运行了多个服务软件,同时提供几种服务。每种服务都打开一个Socket,并绑定到一个端口上,不同的端口对应于不同的服务。Socket正如其英文原意那样,像一个多孔插座。一台主机犹如布满各种插座的房间,每个插座有一个编号,有的插座提供220伏交流电, 有的提供110伏交流电,有的则提供有线电视节目。 客户软件将插头插到不同编号的插座,就可以得到不同的服务。应用程序通常通过”套接字”向网络发出请求或者应答网络请求。(百度百科)
Socket是网络上运行的两个程序间双向通信的一端,这既可以接受请求,也可以发送请求。可以认为Socket是应用程序创建的一个港口码头,应用程序只要把装在货物的集装箱(要发送的数据)放在码头上,就算完成了货物的运送,剩下的工作就由货运公司(驱动程序)去处理了。对接收方来说,应用程序也要创建一个码头,然后就一直等待该码头的货物到达,最后从码头上取走货物(数据)。
java.net.Socket类是Java的基础类,用于执行客户端TCP(传输控制流协议)的操作。套接字有两种:一种套接字在服务器创建的,叫做服务器套接字(ServerSocket);还有一种在客户端被创建的,叫做客户端套接字(Socket)。
客户端指定的端口号一定要和这个port一样。这些构造方法允许指定端口,用来保存到来连接请求队列的长度,绑定本地网络的地址。默认最大连接数目queuelength为50。
(1) public Socket(String host, int port) throws unknowHostException, IOException
(2) public Socket(InetAddress host, int port) throws IOException
建立一个到服务器host、端口号port的套接字,连接到远程主机。
在本程序中,客户端从命令行输入一个半径值并传送到服务器。服务器根据这个半径值,计算出圆面积发送给客户,客户端显示这个值;客户端输入“end”命令结束通信。
//--------------- 文件名:Server.java ---------------------------------
package _4_3Socket编程示例._4_4_1一对多的通信;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
/**
* _4_3Socket编程示例._4_4_1一对多的通信
* Copyright ? 2016 Authors. All rights reserved.
*
* FileName: Server.java
* @author : Wu_Being <1040003585@qq.com>
* Date/Time: 2016-6-7/下午02:35:45
* Description: 服务器端接收客户端圆半径,计算圆的面积
*/
public class Server {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
System.out.println("S等待连接......");
try {
//创建端口号9955的服务器套接字
ServerSocket serverSocket = new ServerSocket(9955);
//监听来处客户的连接请求
Socket socket=serverSocket.accept();
System.out.println("S连接请求来自:"+socket.getInetAddress().getHostAddress());
//创建数据输入输出流
DataInputStream dis=new DataInputStream(socket.getInputStream());
DataOutputStream dos=new DataOutputStream(socket.getOutputStream());
boolean goon=true;
while(goon){
String string=dis.readUTF(); //从socket中读取数据
if(string.equals("end")==false){
string=dealWith(string); //服务器执行特定功能
dos.writeUTF(string); //向socket dos写数据
dos.flush(); //清空缓冲区,立即发送
System.out.println("S计算结果("+string+")已经发送");
}else{
goon=false;
dos.writeUTF("end");
dos.flush();
}
}
//关闭socket和流
serverSocket.close();
dis.close();
dos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 服务器执行特定功能函数,这里是个计算圆面积的功能
* @param string 圆的半径
* @return 圆的面积
*/
public static String dealWith(String string){
System.out.print("S接收到的半径值("+string+");");
double radius=0.0;
try {
radius=Double.parseDouble(string);
} catch (NumberFormatException e) {
return "输入数据格式不对!";
}
if(radius<0)return "数据数据不能小于0!";
double area=radius*radius*Math.PI;
return Double.toString(area);
}
}
//--------------- 文件名:Client.java ---------------------------------
package _4_3Socket编程示例._4_4_1一对多的通信;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
import java.net.UnknownHostException;
/**
* _4_3Socket编程示例._4_4_1一对多的通信
* Copyright ? 2016 Authors. All rights reserved.
*
* FileName: Client.java
* @author : Wu_Being <1040003585@qq.com>
* Date/Time: 2016-6-7/下午02:07:58
* Description: 客户端输入圆的半径,发送到用服务器计算,并收到结果
*/
public class Client {
public static void main(String[] args) {
try {
//创建连接到服务器的socket,服务器IP和端口如下
Socket socket = new Socket("localhost",9955);
//将数据输入输出流连接到socket上
DataInputStream dis = new DataInputStream(socket.getInputStream());
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
System.out.println("C输入半径数值发送到服务器,输入end结束");
boolean goon=true;
//数据从终端输入
BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
//反复读用户的数据并计算
while(goon){
String outString=bf.readLine(); //数据从终端输入
dos.writeUTF(outString); //写到Socket dos中
dos.flush(); //清空缓冲区,立即发送
String inString=dis.readUTF(); //从Socket dis中读数据
if(!inString.equals("end")){
System.out.println("C从服务器返回的结果是:"+inString);
}else{
goon=false;
System.out.println("C服务结束!!!");
}
}
//关闭socket和流
socket.close();
dis.close();
dos.close();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
这个服务器同时能响应多个客户端请求。ServerSocket对象的accept()方法每当监听到一个连接请求时,就会产生一个Socket对象,所以只要此方法反复监听客户请求,就可以为每一个客户生成一个专用的Socket对象进行通信。服务器主程序和线程程序如下,客户端程序和一对一通信的Client.java程序一样。
//------------------- 文件名MultiServer.java -----------------------
package _4_3Socket编程示例._4_4_2一对多的通信;
import java.net.ServerSocket;
import java.net.Socket;
/**
* _4_3Socket编程示例._4_4_2一对多的通信;
* Copyright ? 2016 Authors. All rights reserved.
*
* FileName: MultiServer.java
* @author : Wu_Being <1040003585@qq.com>
* Date/Time: 2016-6-7/下午11:12:51
* Description: 这是主程序,它只要简单地启动线程就可以了。
*/
public class MultiServer {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
System.out.println("S启动......");
try {
ServerSocket serverSocket = new ServerSocket(9955);
Socket connectToClientSocket=null;
int i=0;
while(++i!=0){
//等待客户端的请求
connectToClientSocket=serverSocket.accept();
//每次请求都启动一个线程来处理
new ServerThread(connectToClientSocket,i);
}
}
catch (Exception e) {
}
}
}
//------------------- 文件名ServerThread.java -----------------------
package _4_3Socket编程示例._4_4_2一对多的通信;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
/**
* _4_3Socket编程示例._4_4_2一对多的通信;
* Copyright ? 2016 Authors. All rights reserved.
*
* FileName: ServerThread.java
* @author : Wu_Being <1040003585@qq.com>
* Date/Time: 2016-6-7/下午11:11:32
* Description: 利用本线程来完成服务器与客户端的通信工程
*/
public class ServerThread extends Thread {
private Socket connectToClientSocket;
private DataInputStream inFromClient;
private DataOutputStream outToClient;
private int clientname=1; //不能用static,否则所用线程会共用clienti变量
/**
* 构造函数1
* @param socket
* @throws IOException
*/
public ServerThread(Socket socket) throws IOException {
super();
connectToClientSocket=socket;
inFromClient=new DataInputStream(connectToClientSocket.getInputStream());
outToClient=new DataOutputStream(connectToClientSocket.getOutputStream());
System.out.println("S连接请求来自:"+connectToClientSocket.getInetAddress().getHostAddress());
start(); //启动run()方法
}
/**
* 构造函数2
* @param socket
* @param ci 连接的第i个客户端
* @throws IOException
*/
public ServerThread(Socket socket,int cname) throws IOException {
super();
connectToClientSocket=socket;
clientname=cname;
inFromClient=new DataInputStream(connectToClientSocket.getInputStream());
outToClient=new DataOutputStream(connectToClientSocket.getOutputStream());
System.out.println("S连接请求来自"+clientname+":"+connectToClientSocket.getInetAddress().getHostAddress());
start(); //启动run()方法
}
/**
* 在run()方法与客户端通信
*/
@Override
public void run() {
// TODO Auto-generated method stub
super.run();
System.out.println("S连接Clinet"+clientname+"......");
try {
boolean goon=true;
while(goon){
String string=inFromClient.readUTF(); //从socket中读取数据
if(string.equals("end")==false){
string=dealWith(string,clientname); //服务器执行特定功能
outToClient.writeUTF(string); //向socket dos写数据
outToClient.flush(); //清空缓冲区,立即发送
System.out.println("SC"+clientname+"计算结果("+string+")已经发送");
}else{
goon=false;
outToClient.writeUTF("end");
outToClient.flush();
System.out.println("SC"+clientname+"服务结束!!!");
}
}
//关闭socket和流
connectToClientSocket.close();
inFromClient.close();
outToClient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 服务器执行特定功能函数,这里是个计算圆面积的功能
* @param string 圆的半径
* @return 圆的面积
*/
public static String dealWith(String string,int ci){
System.out.print("SC"+ci+"接收到的半径值("+string+");");
double radius=0.0;
try {
radius=Double.parseDouble(string);
} catch (NumberFormatException e) {
return "输入数据格式不对!";
}
if(radius<0)return "数据数据不能小于0!";
double area=radius*radius*Math.PI;
return Double.toString(area);
}
}
当用户输入文字时,程序如何接收对方发来的数据?解决的方法是将接收数据放到独立的线程中,它始终在后台运行,一旦对方发来了数据,就立即显示在界面上。而主界面负责输入文字和发送数据,这样发送和接收数据互不影响。
package _4_3Socket编程示例._4_4_4简单的聊天程序;
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
/**
*
* Copyright ? 2016 Authors. All rights reserved.
*
* FileName: .java
* @author : Wu_Being <1040003585@qq.com>
* Date/Time: 2016-6-9/下午12:19:57
* Description:
*/
public class ChatServer implements ActionListener, Runnable {
JTextField msgTextField;
JTextArea showTextArea;
JFrame mainJFrame;
JButton sentButton;
JScrollPane jSPane;
JPanel panel;// 嵌板
Container container;// 容器
Thread thread = null;
ServerSocket serverSocket;
Socket connectToClientSocket;
DataInputStream inFromClient;
DataOutputStream outToClient;
public ChatServer() {
// 设置界面,包含容器
mainJFrame = new JFrame("聊天——服务器端(黑猫顺:爱上你的专业,精通专业技能。)");
container = mainJFrame.getContentPane();
// 聊天信息展示框
showTextArea = new JTextArea();
showTextArea.setEditable(false); // 不可编辑
showTextArea.setLineWrap(true); // 自动换行
jSPane = new JScrollPane(showTextArea);
// 聊天信息输入框
msgTextField = new JTextField();
msgTextField.setColumns(30); // 输入框长度
msgTextField.addActionListener(this);/**/// ?
// 发送按键
sentButton = new JButton("发送");
sentButton.addActionListener(this);/**/
// 嵌板有聊天信息输入框和发送按键
panel = new JPanel();
panel.setLayout(new FlowLayout());
panel.add(msgTextField);
panel.add(sentButton);
// 容器包含聊天信息展示框和嵌板
container.add(jSPane, BorderLayout.CENTER);
container.add(panel, BorderLayout.SOUTH);
// 主界面,要定义在后面
mainJFrame.setSize(500, 400);
mainJFrame.setVisible(true);
mainJFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
try {
// 创建服务器套接字
serverSocket = new ServerSocket(9955);
showTextArea.append("正在等待对话请求..." + getTime() + "\n");
// 监听客户端的连接
connectToClientSocket = serverSocket.accept();
inFromClient = new DataInputStream(connectToClientSocket
.getInputStream());
outToClient = new DataOutputStream(connectToClientSocket
.getOutputStream());
// 启动线程在后台来接收对方的消息
thread = new Thread(this);
thread.setPriority(Thread.MAX_PRIORITY);
thread.start();
/**
* public final static int MIN_PRIORITY = 1; public final static int
* NORM_PRIORITY = 5; public final static int MAX_PRIORITY = 10;
* setPriority的参数在1 - 10 之间就可以, 否则会抛异常.
* setPriority不一定起作用的,在不同的操作系统不同的jvm上,效果也可能不同。
* 现在很多jvm的线程的实现都使用的操作系统线程,设置优先级也是使用的操作系统优先级,
* java层面有10个优先级别,假设操作系统只有3个优先级别, 那么jvm可能将1-4级映射到操作系统的1级,
* 5-7级映射到操作系统的2级, 剩下的映射到3级,这样的话,在java层面,将优先级设置为5,6,7,其实本质就是一样的了。
*/
} catch (IOException e) {
showTextArea.append("对不起,不能创建服务器" + getTime() + "");
msgTextField.setEditable(false); // 不可编辑
msgTextField.setEnabled(false); // 不可见
}
}
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
new ChatServer();
}
// 响应按钮事件,发送消息给对方
@Override
public void actionPerformed(ActionEvent e) {
String string = msgTextField.getText();
if (string.length() > 0) {
try {
outToClient.writeUTF(string);
outToClient.flush();
showTextArea.append("黑猫顺说(" + string + ")" + getTime() + "\n");
msgTextField.setText(null);
} catch (IOException e1) {
showTextArea.append("你的消息(" + string + ")未能发送出去" + getTime()
+ "\n");
}
}
}
@Override
public void run() {
try {
while (true) {
showTextArea.append("吴兵说(" + inFromClient.readUTF() + ")"
+ getTime() + "\n");
Thread.sleep(1000);
}
} catch (IOException e) {
} catch (InterruptedException e) {
thread.start();
}
}
/**
* Java代码中获得当前时间
*
* @return 当前时期时间
*/
private String getTime() {
Date date = new Date();
DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String time = format.format(date);
return time;
}
}
package _4_3Socket编程示例._4_4_4简单的聊天程序;
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
/**
*
* Copyright ? 2016 Authors. All rights reserved.
*
* FileName: .java
* @author : Wu_Being <1040003585@qq.com>
* Date/Time: 2016-6-9/下午12:20:18
* Description:
*/
public class ChatClient implements ActionListener, Runnable {
JTextField msgTextField;
JTextArea showTextArea;
JFrame mainJFrame;
JButton sentButton;
JScrollPane jSPane;
JPanel panel;// 嵌板
Container container;// 容器
Thread thread = null;
ServerSocket serverSocket;
Socket connectToClientSocket;
DataInputStream inFromClient;
DataOutputStream outToClient;
public ChatClient() {
// 设置界面,包含容器
mainJFrame = new JFrame("聊天——客户端(吴兵)");
container = mainJFrame.getContentPane();
// 聊天信息展示框
showTextArea = new JTextArea();
showTextArea.setEditable(false); // 不可编辑
showTextArea.setLineWrap(true); // 自动换行
jSPane = new JScrollPane(showTextArea);
// 聊天信息输入框
msgTextField = new JTextField();
msgTextField.setColumns(30); // 输入框长度
msgTextField.addActionListener(this);/**/// ?
// 发送按键
sentButton = new JButton("发送");
sentButton.addActionListener(this);/**/
// 嵌板有聊天信息输入框和发送按键
panel = new JPanel();
panel.setLayout(new FlowLayout());
panel.add(msgTextField);
panel.add(sentButton);
// 容器包含聊天信息展示框和嵌板
container.add(jSPane, BorderLayout.CENTER);
container.add(panel, BorderLayout.SOUTH);
// 主界面,要定义在后面
mainJFrame.setSize(500, 400);
mainJFrame.setVisible(true);
mainJFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
try {
// 创建套接字连接到服务器
connectToClientSocket = new Socket("localhost", 9955);/**/
inFromClient = new DataInputStream(connectToClientSocket
.getInputStream());
outToClient = new DataOutputStream(connectToClientSocket
.getOutputStream());
showTextArea.append("连接成功,请说话..." + getTime() + "\n");
// 创建线程在后台来接收对方的消息
thread = new Thread(this);
thread.setPriority(Thread.MAX_PRIORITY);
thread.start();
} catch (IOException e) {
showTextArea.append("对不起,没能连接到服务器" + getTime() + "");
msgTextField.setEditable(false); // 不可编辑
msgTextField.setEnabled(false); // 不可见
}
}
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
new ChatClient();
}
// 响应按钮事件,发送消息给对方
@Override
public void actionPerformed(ActionEvent e) {
String string = msgTextField.getText();
if (string.length() > 0) {
try {
outToClient.writeUTF(string);
outToClient.flush();
showTextArea.append("吴兵说(" + string + ")" + getTime() + "\n");
msgTextField.setText(null);
} catch (IOException e1) {
showTextArea.append("你的消息(" + string + ")未能发送出去" + getTime()
+ "\n");
}
}
}
@Override
public void run() {
try {
while (true) {
showTextArea.append("黑猫顺说(" + inFromClient.readUTF() + ")"
+ getTime() + "\n");
Thread.sleep(1000);
}
} catch (IOException e) {
} catch (InterruptedException e) {
thread.start();
}
}
/**
* Java代码中获得当前时间
*
* @return 当前时期时间
*/
private String getTime() {
Date date = new Date();
DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String time = format.format(date);
return time;
}
}
标签:
原文地址:http://blog.csdn.net/u014134180/article/details/51615011