Java网络socket编程详解.docx
- 文档编号:5444111
- 上传时间:2022-12-16
- 格式:DOCX
- 页数:23
- 大小:25.85KB
Java网络socket编程详解.docx
《Java网络socket编程详解.docx》由会员分享,可在线阅读,更多相关《Java网络socket编程详解.docx(23页珍藏版)》请在冰豆网上搜索。
Java网络socket编程详解
7.2面向套接字编程
我们已经通过了解Socket的接口,知其所以然,下面我们就将通过具体的案例,来熟悉Socket的具体工作方式
7.2.1使用套接字实现基于TCP协议的服务器和客户机程序
依据TCP协议,在C/S架构的通讯过程中,客户端和服务器的Socket动作如下:
客户端:
1.用服务器的IP地址和端口号实例化Socket对象。
2.调用connect方法,连接到服务器上。
3.将发送到服务器的IO流填充到IO对象里,比如BufferedReader/PrintWriter。
4.利用Socket提供的getInputStream和getOutputStream方法,通过IO流对象,向服务器发送数据流。
5.通讯完成后,关闭打开的IO对象和Socket。
服务器:
1.在服务器,用一个端口来实例化一个ServerSocket对象。
此时,服务器就可以这个端口时刻监听从客户端发来的连接请求。
2.调用ServerSocket的accept方法,开始监听连接从端口上发来的连接请求。
3.利用accept方法返回的客户端的Socket对象,进行读写IO的操作
通讯完成后,关闭打开的流和Socket对象。
7.2.1.1开发客户端代码
根据上面描述的通讯流程,我们可以按如下的步骤设计服务器端的代码。
第一步,依次点击Eclipse环境里的“文件”|“新建”|“项目”选项,进入“新建项目”的向导对话框,在其中选中“Java项目”,点击“下一步”按钮,在随后弹出的对话框里,在其中的“项目名”一栏里,输入项目名“TCPSocket”,其它的选项目
选择系统默认值,再按“完成”按钮,结束创建Java项目的动作。
第二步,完成创建项目后,选中集成开发环境左侧的项目名“TCPSocket”,点击右键,在随后弹出的菜单里依次选择“新建”!
“类”的选项,创建服务器类的代码。
在随后弹出的“新建Java类”的对话框里,输入包名“tcp”,输入文件名“ServerCode”,请注意大小写,在“修饰符”里选中“公用”,在“想要创建哪些方法存根”下,选中“publicstaticvoidmain(String[]args)”单选框,同时把其它两项目取消掉,再按“完成”按钮,可以生成代码。
第三步,在生成的代码里,编写引入Java包的代码,只有当我们引入这些包后,我们才能调用这些包里提供的IO和Socket类的方法。
packagetcp;
importjava.io.BufferedReader;
importjava.io.BufferedWriter;
importjava.io.IOException;
importjava.io.InputStreamReader;
importjava.io.OutputStreamWriter;
importjava.io.PrintWriter;
import.ServerSocket;
import.Socket;
第四步,编写服务器端的主体代码,如下所示。
publicclassServerCode
{
//设置端口号
publicstaticintportNo=3333;
publicstaticvoidmain(String[]args)throwsIOException
{
ServerSockets=newServerSocket(portNo);
System.out.println("TheServerisstart:
"+s);
//阻塞,直到有客户端连接
Socketsocket=s.accept();
try
{
System.out.println("AccepttheClient:
"+socket);
//设置IO句柄
BufferedReaderin=newBufferedReader(newInputStreamReader(socket
.getInputStream()));
PrintWriterout=newPrintWriter(newBufferedWriter(
newOutputStreamWriter(socket.getOutputStream())),true);
while(true)
{
Stringstr=in.readLine();
if(str.equals("byebye"))
{
break;
}
System.out.println("InServerreveivedtheinfo:
"+str);
out.println(str);
}
}
finally
{
System.out.println("closetheServersocketandtheio.");
socket.close();
s.close();
}
}
}
这段代码的主要业务逻辑是:
1.在上述代码里的main函数前,我们设置了通讯所用到的端口号,为3333。
2.在main函数里,根据给定3333端口号,初始化一个ServerSocket对象s,该对象用来承担服务器端监听连接和提供通讯服务的功能。
3.调用ServerSocket对象的accept方法,监听从客户端的连接请求。
当完成调用accept方法后,整段服务器端代码将回阻塞在这里,直到客户端发来connect请求。
4.当客户端发来connect请求,或是通过构造函数直接把客户端的Socket对象连接到服务器端后,阻塞于此的代码将会继续运行。
此时服务器端将会根据accept方法的执行结果,用一个Socket对象来描述客户端的连接句柄。
5.创建两个名为in和out的对象,用来传输和接收通讯时的数据流。
6.创建一个while(true)的死循环,在这个循环里,通过in.readLine()方法,读取从客户端发送来的IO流(字符串),并打印出来。
如果读到的字符串是“byebye”,那么退出while循环。
7.在try…catch…finally语句段里,不论在try语句段里是否发生异常,并且不论这些异常的种类,finally从句都将会被执行到。
在finally从句里,将关闭描述客户端的连接句柄socket对象和ServerSocket类型的s对象。
7.2.1.2开发客户端代码
我们可以按以下的步骤,开发客户端的代码。
第一,在TCPSocket项目下的tcp包下,创建一个名为ClientCode.java的文件。
在其中编写引入Java包的代码,如下所示:
packagetcp;
importjava.io.BufferedReader;
importjava.io.BufferedWriter;
importjava.io.IOException;
importjava.io.InputStreamReader;
importjava.io.OutputStreamWriter;
importjava.io.PrintWriter;
import.InetAddress;
import.Socket;
第二,编写客户端的主体代码,如下所示:
publicclassClientCode
{
staticStringclientName="Mike";
//端口号
publicstaticintportNo=3333;
publicstaticvoidmain(String[]args)throwsIOException
{
//设置连接地址类,连接本地
InetAddressaddr=InetAddress.getByName("localhost");
//要对应服务器端的3333端口号
Socketsocket=newSocket(addr,portNo);
try
{
System.out.println("socket="+socket);
//设置IO句柄
BufferedReaderin=newBufferedReader(newInputStreamReader(socket
.getInputStream()));
PrintWriteout=newPrintWriter(BufferedWriter(newOutputStreamWriter(socket.getOutputStream())),true);
out.println("HelloServer,Iam"+clientName);
Stringstr=in.readLine();
System.out.println(str);
out.println("byebye");
}
finally
{
System.out.println("closetheClientsocketandtheio.");
socket.close();
}
}
}
上述客户端代码的主要业务逻辑是:
1.同样定义了通讯端口号,这里给出的端口号必须要和服务器端的一致。
2.在main函数里,根据地址信息“localhost”,创建一个InetAddress类型的对象addr。
这里,因为我们把客户端和服务器端的代码都放在本机运行,所以同样可以用“127.0.0.1”字符串,来创建InetAddress对象。
3.根据addr和端口号信息,创建一个Socket类型对象,该对象用来同服务器端的ServerSocket类型对象交互,共同完成C/S通讯流程。
4.同样地创建in和out两类IO句柄,用来向服务器端发送和接收数据流。
5.通过out对象,向服务器端发送"HelloServer,Iam…"的字符串。
发送后,同样可以用in句柄,接收从服务器端的消息。
6.利用out对象,发送”byebye”字符串,用以告之服务器端,本次通讯结束。
7.在finally从句里,关闭Socket对象,断开同服务器端的连接。
7.2.1.3运行效果演示
在上述两部分里,我们分别讲述了C/S通讯过程中服务器端和客户端代码的业务逻辑,下面我们将在集成开发环境里,演示这里通讯流程。
第一步,选中ServerCode.java代码,在eclipse的“运行”菜单里,选中“运行方式”|“1Java应用程序”的菜单,开启服务器端的程序。
开启服务端程序后,会在eclipse环境下方的控制台里显示如下的内容:
TheServerisstart:
ServerSocket[addr=0.0.0.0/0.0.0.0,port=0,localport=3333]
在这里,由于ServerSocket对象并没监听到客户端的请求,所以addr和后面的port值都是初始值。
第二步,按同样的方法,打开ClientCode.java程序,启动客户端。
启动以后,将在客户端的控制台里看到如下的信息:
socket=Socket[addr=localhost/127.0.0.1,port=3333,localport=1326]
HelloServer,IamMike
closetheClientsocketandtheio.
从中可以看到,在第一行里,显示客户端Socket对象连接的IP地址和端口号,在第二行里,可以到到客户端向服务器端发送的字符串,而在第三行里,可以看到通讯结束后,客户端关闭连接Socket和IO对象的提示语句。
第三步,在eclipse下方的控制台里,切换到ServerCode服务端的控制台提示信息里,我们可以看到服务器端在接收到客户端连接请求后的响应信息。
响应的信息如下所示:
TheServerisstart:
ServerSocket[addr=0.0.0.0/0.0.0.0,port=0,localport=3333]
AccepttheClient:
Socket[addr=/127.0.0.1,port=1327,localport=3333]
InServerreveivedtheinfo:
HelloServer,IamMike
closetheServersocketandtheio.
其中,第一行是启动服务器程序后显示的信息。
在第二行里,显示从客户端发送的连接请求的各项参数。
在第三行里,显示了从客户端发送过来的字符串。
在第四行里,显示了关闭服务器端ServerSocket和IO对象的提示信息。
从中我们可以看出在服务器端里accept阻塞和继续运行的这个过程。
通过上述的操作,我们可以详细地观察到C/S通讯的全部流程,请大家务必要注意:
一定要先开启服务器端的程序再开启客户端,如果这个步骤做反的话,客户端程序会应找不到服务器端而报异常。
7.2.2使用套接字连接多个客户机
在7.1的代码里,客户端和服务器之间只有一个通讯线程,所以它们之间只有一条Socket信道。
如果我们在通过程序里引入多线程的机制,可让一个服务器端同时监听并接收多个客户端的请求,并同步地为它们提供通讯服务。
基于多线程的通讯方式,将大大地提高服务器端的利用效率,并能使服务器端能具备完善的服务功能。
7.2.2.1开发客户端代码
我们可以按以下的步骤开发基于多线程的服务器端的代码。
第一步,在3.2里创建的“TCPSocket”项目里,新建一个名为ThreadServer.java的代码文件,创建文件的方式大家可以参照3.2部分的描述。
首先编写package和import部分的代码,用来打包和引入包文件,如下所示:
packagetcp;
importjava.io.*;
import.*;
第二步,由于我们在服务器端引入线程机制,所以我们要编写线程代码的主体执行类ServerThreadCode,这个类的代码如下所示:
classServerThreadCodeextendsThread
{
//客户端的socket
privateSocketclientSocket;
//IO句柄
privateBufferedReadersin;
privatePrintWritersout;
//默认的构造函数
publicServerThreadCode()
{}
publicServerThreadCode(Sockets)throwsIOException
{
clientSocket=s;
//初始化sin和sout的句柄
sin=newBufferedReader(newInputStreamReader(clientSocket
.getInputStream()));
sout=newPrintWriter(newBufferedWriter(newOutputStreamWriter(
clientSocket.getOutputStream())),true);
//开启线程
start();
}
//线程执行的主体函数
publicvoidrun()
{
try
{
//用循环来监听通讯内容
for(;;)
{
Stringstr=sin.readLine();
//如果接收到的是byebye,退出本次通讯
if(str.equals("byebye"))
{
break;
}
System.out.println("InServerreveivedtheinfo:
"+str);
sout.println(str);
}
System.out.println("closingtheserversocket!
");
}
catch(IOExceptione)
{
e.printStackTrace();
}
finally
{
System.out.println("closetheServersocketandtheio.");
try
{
clientSocket.close();
}
catch(IOExceptione)
{
e.printStackTrace();
}
}
}
}
这个类的业务逻辑说明如下:
1.这个类通过继承Thread类来实现线程的功能,也就是说,在其中的run方法里,定义了该线程启动后要执行的业务动作。
2.这个类提供了两种类型的重载函数。
在参数类型为Socket的构造函数里,通过参数,初始化了本类里的Socket对象,同时实例化了两类IO对象。
在此基础上,通过start方法,启动定义在run方法内的本线程的业务逻辑。
3.在定义线程主体动作的run方法里,通过一个for(;;)类型的循环,根据IO句柄,读取从Socket信道上传输过来的客户端发送的通讯信息。
如果得到的信息为“byebye”,则表明本次通讯结束,退出for循环。
4.catch从句将处理在try语句里遇到的IO错误等异常,而在finally从句里,将在通讯结束后关闭客户端的Socket句柄。
上述的线程主体代码将会在ThreadServer类里被调用。
第三步,编写服务器端的主体类ThreadServer,代码如下所示:
publicclassThreadServer
{
//端口号
staticfinalintportNo=3333;
publicstaticvoidmain(String[]args)throwsIOException
{
//服务器端的socket
ServerSockets=newServerSocket(portNo);
System.out.println("TheServerisstart:
"+s);
try
{
for(;;)
{
//阻塞,直到有客户端连接
Socketsocket=s.accept();
//通过构造函数,启动线程
newServerThreadCode(socket);
}
}
finally
{
s.close();
}
}
}
这段代码的主要业务逻辑说明如下:
1.首先定义了通讯所用的端口号,为3333。
2.在main函数里,根据端口号,创建一个ServerSocket类型的服务器端的Socket,用来同客户端通讯。
3.在for(;;)的循环里,调用accept方法,监听从客户端请求过来的socket,请注意这里又是一个阻塞。
当客户端有请求过来时,将通过ServerThreadCode的构造函数,创建一个线程类,用来接收客户端发送来的字符串。
在这里我们可以再一次观察ServerThreadCode类,在其中,这个类通过构造函数里的start方法,开启run方法,而在run方法里,是通过sin对象来接收字符串,通过sout对象来输出。
4.在finally从句里,关闭服务器端的Socket,从而结束本次通讯。
7.2.2.2开发客户端代码
我们可以按以下的步骤,编写的基于多线程的客户端代码。
第一步,在“TCPSocket”项目里,新建一个名为ThreadClient.java的代码文件。
同样是编写package和import部分的代码,用来打包和引入包文件,如下所示:
packagetcp;
import.*;
importjava.io.*;
第二步,编写线程执行主体的ClientThreadCode类,同样,这个类通过继承Thread来实现线程的功能。
classClientThreadCodeextendsThread
{
//客户端的socket
privateSocketsocket;
//线程统计数,用来给线程编号
privatestaticintcnt=0;
privateintclientId=cnt++;
privateBufferedReaderin;
privatePrintWriterout;
//构造函数
publicClientThreadCode(InetAddressaddr)
{
try
{
socket=newSocket(addr,3333);
}
catch(IOExceptione)
{
e.printStackTrace();
}
//实例化IO对象
try
{
in=newBufferedReader(
newInputStreamReader(socket.getInputStream()));
out=newPrintWriter(
newBufferedWriter(newOutputStreamWriter(socket.getOutputStream())),true);
//开启线程
start();
}
catch(IOExceptione)
{
//出现异常,关闭socket
try
{
socket.close();
}
catch(IOExceptione2)
{
e2.printStackTrace();
}
}
}
//线程主体方法
publicvoidrun()
{
try
{
out.println("HelloServer,Myidis"+clientId);
Stringstr=in.readLine();
System.out.println(str);
out.println("byebye");
}
catch(IOExceptione)
{
e.printStackTrace();
}
finally
{
try
{
socket.close();
}
catch(IOExceptione)
{
e.printStackTrace();
}
}
}
}
这个类的主要业务逻辑是:
1.在构造函数里,通过参数类型为InetAddress类型参数和3333,初始化了本类里的Socket对象,随后实例化了两类IO对象,并通过start方法,启动定义在run方法内的本线程的业务逻辑。
2.在定义线程主体动作的run方法里,通过IO句柄,向Socket信道上传输本客户端的ID号,发送完毕后,传输”byebye”字符串,向服务器端表示本线程的通讯结束。
3.同
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Java 网络 socket 编程 详解