使用Tcp协议的Socket编程主要用到两个类,Socket和ServerSocket。ServerSocket本身也是一个Socket,只是它同时包含了一些额外的服务器终端的功能,比如监听端口,等待客户端Socket前来建立连接等。通过accept方法一旦和客户端建立起连接,就会返回一个普通的Socket和客户端Socket进行对等的通信。
本文的Demo实现的功能很简单,用户输入消息,发送给服务器,服务器原封不动返回给客户端。原理是:启动一个服务器线程和两个客户端线程。服务器线程监听2000端口,等待客户端进程的连接。客户端线程包括一个发送线程和一个接收线程,发送线程连接服务器的2000端口,将用户输入的消息发送给服务器线程,接收线程监听2001端口,等待服务器返回的数据。服务器通过2000端口收到客户端发来的数据后,将数据原封不动发送到客户端的2001端口,完成通信。
所谓socket 通常也称作”套接字“,用于描述IP地址和端口,是一个通信链的句柄。应用程序通常通过”套接字”向网络发出请求或者应答网络请求。
以J2SDK-1.3为例,Socket和ServerSocket类库位于java.net包中。ServerSocket用于服务器端,Socket是建立网络连接时使用的。在连接成功时,应用程序两端都会产生一个Socket实例,操作这个实例,完成所需的会话。对于一个网络连接来说,套接字是平等的,并没有差别,不因为在服务器端或在客户端而产生不同级别。不管是Socket还是ServerSocket它们的工作都是通过SocketImpl类及其子类完成的。
重要的Socket API
java.net.Socket继承于java.lang.Object,有八个构造器,其方法并不多,下面介绍使用最频繁的三个方法,其它方法大家可以见JDK-1.3文档。
Accept方法用于产生”阻塞”,直到接受到一个连接,并且返回一个客户端的Socket对象实例。”阻塞”是一个术语,它使程序运行暂时”停留”在这个地方,直到一个会话产生,然后程序继续;通常”阻塞”是由循环产生的。
getInputStream方法获得网络连接输入,同时返回一个InputStream对象实例。
getOutputStream方法连接的另一端将得到输入,同时返回一个OutputStream对象实例。
注意:其中getInputStream和getOutputStream方法均会产生一个IOException,它必须被捕获,因为它们返回的流对象,通常都会被另一个流对象使用。
TCP 编程
服务器端套路
1.创建ServerSocket对象,绑定监听端口。
2.通过accept()方法监听客户端请求。
3.连接建立后,通过输入流读取客户端发送的请求信息。
4.通过输出流向客户端发送响应信息。
5.关闭响应的资源。
客户端套路
1.创建Socket对象,指明需要连接的服务器的地址和端口号。
2.连接建立后,通过输出流向服务器发送请求信息。
3.通过输入流获取服务器响应的信息。
4.关闭相应资源。
多线程实现服务器与多客户端之间通信步骤
1.服务器端创建ServerSocket,循环调用accept()等待客户端连接。
2.客户端创建一个socket并请求和服务器端连接。
3.服务器端接受客户端请求,创建socket与该客户建立专线连接。
4.建立连接的两个socket在一个单独的线程上对话。
5.服务器端继续等待新的连接。
首先是服务端代码:
package
ChatTwoPackage;
import
java.io.*;
import
java.net.*;
public
class
ChatTwoServer {
public
ChatTwoServer(
int
port,String name)
throws
IOException
{
ServerSocket server=
new
ServerSocket(port);
//创建seversocket对象,提供tcp连接服务。指定端口port,等待tcp连接。
System.out.print(
"正在等待连接,请勿操作!"
);
Socket client=server.accept();
//创建socket对象,它等待接收客户端的连接。
new
ChatTwoClient(name,client);
//实现图形界面。
server.close();
}
public
static
void
main(String[] args)
throws
IOException {
new
ChatTwoServer(
2001
,
"SQ"
);
}
}
然后是客户端的代码:
package
ChatTwoPackage;
import
java.awt.event.*;
import
javax.swing.*;
import
java.net.*;
import
java.io.*;
public
class
ChatTwoClient
extends
JFrame
implements
ActionListener {
private
String name;
private
JTextArea text_re;
private
JTextField text_se;
private
PrintWriter cout;
private
JButton buttons[];
public
ChatTwoClient(String name,Socket socket)
throws
IOException
{
super
(
"我:"
+name+InetAddress.getLocalHost().getHostAddress()+
":"
+socket.getLocalPort());
this
.setBounds(
320
,
240
,
400
,
240
);
this
.text_re=
new
JTextArea();
this
.text_re.setEditable(
false
);
this
.getContentPane().add(
new
JScrollPane(
this
.text_re));
JToolBar toolBar=
new
JToolBar();
this
.getContentPane().add(toolBar,
"South"
);
toolBar.add(
this
.text_se=
new
JTextField(
30
));
buttons=
new
JButton[
2
];
buttons[
0
]=
new
JButton(
"发送"
);
buttons[
1
]=
new
JButton(
"下线"
);
toolBar.add(buttons[
0
]);
toolBar.add(buttons[
1
]);
buttons[
0
].addActionListener(
this
);
buttons[
1
].addActionListener(
this
);
//给按钮添加事件监听,委托当前对象处理
this
.setVisible(
true
);
this
.name=name;
this
.cout=
new
PrintWriter(socket.getOutputStream(),
true
);
//获得socket输出流
this
.cout.println(name);
BufferedReader br=
new
BufferedReader(
new
InputStreamReader(socket.getInputStream()));
//将socket的字节输入流转换为字符流,默认GBK字符集,再创建缓冲字符输入流
String line=
"连接"
+br.readLine()+
"成功"
;
while
(line!=
null
&&!line.endsWith(
"bye"
))
{
text_re.append(line+
"\r\n"
);
line=br.readLine();
}
//读取对方发送的内容并显示,直到内容为为空或对方下线
br.close();
this
.cout.close();
socket.close();
buttons[
0
].setEnabled(
false
);
buttons[
1
].setEnabled(
false
);
}
public
ChatTwoClient(String name,String host,
int
port)
throws
IOException
{
this
(name,
new
Socket(host,port));
//调用另一个构造方法
}
public
void
actionPerformed(ActionEvent ev)
{
if
(ev.getActionCommand().equals(
"发送"
))
{
this
.cout.println(name+
":"
+text_se.getText());
text_re.append(
"我:"
+text_se.getText()+
"\n"
);
text_se.setText(
""
);
}
//按下发送按钮后,将内容发出,并更新自己聊天框的内容
if
(ev.getActionCommand().equals(
"下线"
))
{
text_re.append(
"你已下线\n"
);
this
.cout.println(name+
"离线\n"
+
"bye\n"
);
buttons[
0
].setEnabled(
false
);
buttons[
1
].setEnabled(
false
);
}
//下线按钮按下后,发送bye作为下线标记
}
public
static
void
main(String[] args)
throws
IOException {
new
ChatTwoClient(
"mxl"
,
"127.0.0.1"
,
2001
);
//ip地址和端口
}
}
运行效果: