JAVA之Socket编程,
JAVA之Socket编程,
Socket:英文原义是“孔”或“插座”。在这里作为4BDS UNIX的进程通信机制。socket非常类似于电话插座,以一个国家级电话网为例:电话的通话双方相当于相互通信的2个进程,区号是它的网络地址;区内的交换机相当于主机,主机分配给每个用户局内的号码相当于socket号。任何用户在通话之前,首先要占有一部电话机,相当于申请一个socket;同时要知道对方的号码,相当于对方有一个固定的socket。然后向对方拨号呼叫,相当于发出连接请求(假如对方不在同一区内,还要拨对方区号,相当于给出网络地址)。对方假如在场并空闲(相当于通信的另一主机开机且可以接受连接请求),拿起电话话筒,双方就可以正式通话,相当于连接成功。双方通话的过程,是一方向电话机发出信号和对方从电话机接收信号的过程,相当于向socket发送数据和从socket接收数据。通话结束后,一方挂起电话机相当于关闭socket,撤消连接。
一、通信方式:
1、一个客户端连接一个服务器,或称为“点对点”(peer to peer);
2、多个客户端连接一个服务器,这就是最常见的方式;
3、一个客户端连接多个服务器,这种方式很少见,主要用于一个客户端向多个服务器发送请求;
二、连接方式:
1、短连接:连接—>传输数据—>关闭连接,也就是说当数据传输完之后会马上将连接关闭;一般在客户端数量较多而连接又比较频繁时采用短连接,常用于多个客户端连接一个服务器。
2、长连接:连接—>传输数据—>保持连接—>传输数据—>保持连接……—>关闭连接,也就是说当数据传输完之后不会马上将连接关闭,而继续用该连接传输后续的数据。一般在客户端数量较少但连接又比较频繁时采用长连接,常用于点对点通信。
三、发送接收方式:
1、异步:报文发送和接收是分开的,相互独立的,互不影响。这种方式又分两种情况:
1)异步双工:接收和发送在同一个程序中,有两个不同的子进程分别负责发送和接收;
2)异步单工:接收和发送是用两个不同的程序来完成。
2、同步:发送报文和接收报文是同步进行的,即报文发送后等待接收返回报文。同步方式一般需要考虑超时问题,即报文发出后不能无限等待,需要设定超时时间,超过了这个时间发送方就不再等待读返回报文,直接通知超时返回。
四、读取报文的方式:
1、阻塞与非阻塞方式:
1)阻塞式:如果没有报文接收到,则读函数一直处于等待状态,直到有报文到达。
2)非阻塞式:读函数不停地进行读动作,如果没有报文接收到,等待一段时间后超时返回,这种情况一般需要指定超时时间。
2、一次性读写与循环读写方式:
1)一次性读写:在接收或发送报文动作中一次性不加分别地全部读取或全部发送报文字节。
2)不指定长度循环读写:一般发生在短连接中,受网络路由等限制,一次较长的报文可能在网络传输过程中被分解成了好几个包。一次读取可能不能全部读完一次报文,这就需要循环读报文,直到读完为止。
3)带长度报文头的循环读写方式:这种情况一般是在长连接中,由于在长连接中没有条件能够判断循环读写什么时候结束,所以必须要加长度报文头。读函数先是读取报文头的长度,再根据这个长度去读报文。实际情况中,报头的码制格式还经常不一样,如果是非ASCII码的报文头,还必须转换成ASCII,常见的报文头码制有:
1>n个字节的ASCII码;
2>n个字节的BCD码;
3>n个字节的网络整型码
五、常用方法:
1、服务器端:在客户/服务器通信模式中, 服务器端需要创建监听端口的ServerSocket,ServerSocket负责接收客户连接请求。常用构造方法有:
①ServerSocket() throws IOException:该方法创建ServerSocket时不与任何端口绑定,而之后需要通过bind()方法与特定端口绑定。用途是:允许服务器在绑定到特定端口之前,先设置ServerSocket的一些属性。因为一旦服务器与特定端口绑定,有些属性就不能再改变了。
②ServerSocket(int port) throws IOException
③ServerSocket(int port, int backlog) throws IOException
④ServerSocket(int port, int backlog, InetAddress bindAddr) throws IOException
参数port指定服务器要绑定的端口(服务器要监听的端口),参数backlog指定客户连接请求队列的长度,参数bindAddr指定服务器要绑定的IP地址。
1)ServerSocket 属性
ServerSocket主要有以下 3 个属性:
①SO_TIMEOUT: 表示等待客户连接的超时时间。表示accept()方法等待客户连接的超时时间,以毫秒为单位。如果SO_TIMEOUT的值为0,表示永远不会超时,这是SO_TIMEOUT的默认值。
②SO_REUSEADDR: 表示是否允许重用服务器所绑定的地址,即:是否允许新的ServerSocket绑定到与旧的ServerSocket同样的端口上。SO_REUSEADDR选项的默认值与操作系统有关,在某些操作系统中,允许重用端口,而在某些操作系统中不允许重用端口。
③SO_RCVBUF: 表示接收数据的缓冲区的大小,以字节为单位。一般说来,传输大的连续的数据块(基于HTTP 或 FTP 协议的数据传输) 可以使用较大的缓冲区,这可以减少传输数据的次数,从而提高传输数据的效率。而对于交互频繁且单次传送数量比较小的通信(Telnet和网络游戏),则应该采用小的缓冲区,确保能及时把小批量的数据发送给对方。
无论在 ServerSocket绑定到特定端口之前或之后,调用 setReceiveBufferSize() 方法都有效。例外情况下是如果要设置大于 64 KB 的缓冲区,则必须在 ServerSocket 绑定到特定端口之前进行设置才有效。
④性能偏好:见客户端属性。
2)绑定端口
如果在构造方法中未绑定端口,可以使用:
public void bind(SocketAddress endpoint)
进行绑定。如果运行时无法绑定到端口,会抛出IOException,更确切地说是抛出 BindException,它是IOException的子类。BindException一般是由以下原因造成的:
①端口已经被其他服务器进程占用;
②在某些操作系统中,如果没有以超级用户的身份来运行服务器程序,那么操作系统不允许服务器绑定到1-1023之间的端口。
如果把参数port设为0,表示由操作系统来为服务器分配一个任意可用的端口。有操作系统分配的端口也称为匿名端口。对于多数服务器,会使用明确的端口,而不会使用匿名端口,因为客户程序需要事先知道服务器的端口,才能方便地访问服务器。但在某些场合,匿名端口有着特殊的用途。FTP(文件传输协议)就使用了匿名端口。FTP协议用于在本地文件系统与远程文件系统之间传送文件。FTP使用两个并行的TCP连接:一个是控制连接,一个是数据连接。控制连接用于在客户和服务器之间发送控制信息,如用户名和口令、改变远程目录的命令或上传和下载文件的命令。数据连接用于传送而文件。TCP服务器在21端口上监听控制连接,如果有客户要求上传或下载文件,就另外建立一个数据连接,通过它来传送文件。数据连接的建立有两种方式:
①TCP服务器在20 端口上监听数据连接,TCP客户主动请求建立与该端口的连接。
②首先由TCP 客户端创建一个监听匿名端口的ServerSocket,再把这个 ServerSocket 监听的端口号发送给TCP服务器,然后由TCP 服务器主动请求建立与客户端的连接,这种方式就使用了匿名端口。
3)设置客户连接请求队列的长度
backlog参数用来显式设置连接请求队列的长度,它将覆盖操作系统限定的队列的最大长度。值得注意的是,在以下几种情况中,仍然会采用操作系统限定的队列的最大长度:
①backlog参数的值大于操作系统限定的队列的最大长度;
②backlog参数的值小于或等于0;
③在ServerSocket构造方法中没有设置backlog参数。
当服务器进程运行时,可能会同时监听到多个客户的连接请求。例如,每当一个客户进程执行以下代码:
Socket socket = new Socket("www.baidu.com", 80);
就意味着在远程www.baidu.com主机的80端口上,监听到了一个客户的连接请求。管理客户连接请求的任务是由操作系统来完成的。操作系统把这些连接请求存储在一个先进先出的队列中。许多操作系统限定了队列的最大长度,一般为50。当队列中的连接请求达到了队列的最大容量时,服务器进程所在的主机会拒绝新的连接请求。只有当服务器进程通过ServerSocket的accept()方法从队列中取出连接请求,使队列腾出空位时,队列才能继续加入新的连接请求。
对于客户进程,如果它发出的连接请求被加入到服务器的请求连接队列中,就意味着客户与服务器的连接建立成功,客户进程从Socket构造方法中正常返回。如果客户进程发出的连接请求被服务器拒绝,Socket构造方法就会抛出ConnectionException。
注意:创建绑定端口的服务器进程后,当客户进程的Socket构造方法返回成功,表示客户进程的连接请求被加入到服务器进程的请求连接队列中。虽然客户端成功返回Socket对象,但是还没跟服务器进程形成一条通信线路。必须在服务器进程通过ServerSocket的accept()方法从请求连接队列中取出连接请求,并返回一个Socket对象后,服务器进程这个Socket对象才与客户端的Socket对象形成一条通信线路。
4)绑定IP地址
如果主机只有一个IP地址,那么默认情况下,服务器程序就与该IP地址绑定。ServerSocket的构造方法ServerSocket(intport,intbacklog,InetAddressbingAddr)中有一个bindAddr参数,它显式指定服务器要绑定的IP地址,该构造方法适用于具有多个IP地址的主机。假定一个主机有两个网卡,一个网卡用于连接到Internet,IP为222.67.5.94,还有一个网卡用于连接到本地局域网,IP地址为192.168.3.4。如果服务器仅仅被本地局域网中的客户访问,那么可以按如下方式创建ServerSocket:
ServerSocket serverSocket = new ServerSocket(8000, 10, InetAddress.getByName("192.168.3.4"));
5)获取ServerSocket的信息
①public InetAddress getInetAddress();获取服务器绑定的IP地址
②public int getLocalPort();获取服务器绑定的端口
6)接收客户端的信息
public Socket accept();从连接请求队列中取出一个客户的连接请求,然后创建与客户连接的Socket对象,并将它返回。如果队列中没有连接请求,accept()方法就会一直等待,直到接收到了连接请求才返回。接下来服务器从Socket对象中获得输入流和输出流,就能与客户交换数据。当服务器正在进行发送数据的操作时,如果客户端断开了连接,那么服务器端会抛出一个IOException的子类SocketException异常:java.net.SocketException:Connection reset by peer。这只是服务器与单个客户通信中出现的异常, 这种异常应该被捕获, 使得服务器能继续与其他客户通信。
7)关闭ServerSocketpublic void close();使服务器释放占用的端口,并且断开与所有客户的连接。当一个服务器程序运行结束时,即使没有执行ServerSocket的close()方法,操作系统也会释放这个服务器占用的端口。因此服务器程序不一定要在结束之前执行该方法。
在某些情况下,如果希望及时释放服务器的端口,以便让其他程序能占用该端口,则可以显式调用ServerSocket的close()方法。例如,以下代码用于扫描1-65535之间的端口号。如果ServerSocket成功创建,意味这该端口未被其他服务器进程绑定,否则说明该端口已经被其他进程占用:
for(int port = 1; port <= 65335; port ++){
try{
ServerSocket serverSocket = new ServerSocket(port);
serverSocket.close(); //及时关闭ServerSocket
}catch(IOException e){
System.out.println("端口" + port + " 已经被其他服务器进程占用");
}
}
ServerSocket的isClosed()方法判断ServerSocket是否关闭,只有执行了ServerSocket的close()方法,isClosed()方法才返回true;否则即使ServerSocket还没有和特定端口绑定,isClosed()也会返回false。
ServerSocket的isBound()方法判断ServerSocket是否已经与一个端口绑定,只要ServerSocket已经与一个端口绑定,即使它已经被关闭,isBound()方法也会返回true。
当ServerSocket关闭时,如果网络上还有发送到这个ServerSocket的数据,这个ServerSocket不会立即释放本地端口,而是会等待一段时间,确保接收到了网络上发送过来的延迟数据,然后再释放端口。
2、客户端:
常用构造方法有:
①Socket();
②Socket(InetAddress address, int port);
③Socket(InetAddress address, int port, InetAddress localAddr, int localPort);
④Socket(String host, int port);
⑤Socket(String host, int port, InetAddress localAddr, int localPort);
除第一个不带参数的构造方法以外,其他构造方法都会试图建立与服务器的连接,如果连接成功,就返回Socket对象。如果因为某些原因连接失败就会抛出IOException。1)Socket 属性
Socket主要有以下 3 个属性:
1>TCP_NODELAY:默认情况下发送数据采用Negale算法。Negale算法是指发送方发送的数据不会立即发出,而是先放在缓冲区,等缓存区满了再发出。发送完一批数据后,会等待接收方对这批数据的回应,然后再发送下一批数据。Negale算法适用于发送方需要发送大批量数据,并且接收方会及时作出回应的场合,这种算法通过减少传输数据的次数来提高通信效率。如果发送方持续地发送小批量的数据,并且接收方不一定会立即发送响应数据,那么Negale算法会使发送方运行很慢。对于GUI程序,如网络游戏程序(服务器需要实时跟踪客户端鼠标的移动),这个问题尤其突出。客户端鼠标位置改动的信息需要实时发送到服务器上,由于Negale算法采用缓冲,大大减低了实时响应速度,导致客户程序运行很慢。TCP_NODELAY的默认值为false,表示采用Negale算法。如果调用setTcpNoDelay(true)方法,就会关闭Socket的缓冲确保数据及时发送。如果Socket的底层实现不支持TCP_NODELAY选项,那么getTcpNoDelay()和setTcpNoDelay方法会抛出SocketException。
2>SO_REUSEADDR:当接收方通过Socket的close()方法关闭Socket时,如果网络上还有发送到这个Socket的数据,那么底层的Socket不会立即释放本地端口,而是会等待一段时间,确保接收到了网络上发送过来的延迟数据,然后再释放端口。Socket接收到延迟数据后,不会对这些数据作任何处理。Socket接收延迟数据的目的是,确保这些数据不会被其他碰巧绑定到同样端口的新进程接收到。客户程序一般采用随机端口,因此出现两个客户程序绑定到同样端口的可能性不大。但服务器程序都使用固定的端口,当服务器程序关闭后,有可能它的端口还会被占用一段时间,如果此时立刻在同一个主机上重启服务器程序,由于端口已经被占用,使得服务器程序无法绑定到该端口,启动失败。为了确保一个进程关闭Socket后,即使它还没释放端口,同一个主机上的其他进程还可以立即重用该端口,可以调用Socket的setReuseAddress(true)方法。该方法必须在Socket还没有绑定到一个本地端口之前调用,否则执行setRsuseAddress(true)方法无效。因此必须按照以下方式创建Socket对象,然后再连接远程服务器:
Socket socket = new Socket();
socket.setReuseAddress(true);
SocketAddress remoteAddr = new InetSocketAddress("www.baidu.com",8000);
//绑定本地的匿名端口并且连接到远程服务器
socket.connect(remoteAddr);
或者:socket.setReuseAddress(true);
SocketAddress localAddr = new InetSocketAddress("localhost",9000);
SocketAddress remoteAddr = new InetSocketAddress("www.baidu.com",8000);
//绑定本地端口
socket.bind(localAddr);
//连接远程服务器
socket.connect(remoteAddr);
此外两个共用同一个端口的进程必须都调用setReuseAddress(true)方法,才能使得一个进程关闭Socket后,另一个进程的Socket能够立即重用相同端口。
3>SO_TIMEOUT:用于设定接收数据的等待超时时间,单位为毫秒,它的默认值为0,表示会无限等待永远不会超时。Socket的setSoTimeout()方法必须在接收数据之前执行才有效。此外当输入流的read()方法抛出SocketTimeoutException后Socket仍然是连接的,可以尝试再次读数据。
4>SO_LINGER:用来控制Socket关闭时的行为。默认情况下,如果未设置SO_LINGER选项,getSoLinger()返回的结果是-1,执行Socket的close()方法,该方法会立即返回,但底层的Socket实际上并不立即关闭,它会延迟一段时间,直到发送完所有剩余的数据,才会真正关闭Socket,断开连接。如果执行以下方法:
socket.setSoLinger(true, 3600);
getSoLinger()返回的结果是3600,当执行Socket的close()方法,该方法不会立即返回,而是进入阻塞状态。同时底层的Socket会尝试发送剩余的数据。只有满足以下两个条件之一,close()方法才返回:
①底层的Socket已经发送完所有的剩余数据;
②底层的Socket还没有发送完所有的剩余数据,但超过3600秒,剩余未发送的数据被丢弃然后close()方法返回。
在以上两种情况中,当close()方法返回后,底层的Socket会被关闭,断开连接。此外setSoLinger(booleanon,intseconds)方法中的seconds参数以秒为单位,而不是以毫秒为单位。
5>SO_RCVBUF:表示Socket的用于输入数据的缓冲区的大小。一般说来,传输大的连续的数据块(基于HTTP或FTP协议的通信)可以使用较大的缓冲区,这可以减少传输数据的次数,提高传输数据的效率。而对于交互频繁且单次传送数据量比较小的通信方式(Telnet和网络游戏),则应该采用小的缓冲区,确保小批量的数据能及时发送给对方。这种设定缓冲区大小的原则也同样适用于Socket的SO_SNDBUF选项。如果底层 Socket 不支持 SO_RCVBUF 选项,那么setReceiveBufferSize()方法会抛出 SocketException。
6>SO_SNDBUF:表示Socket 的用于输出数据的缓冲区的大小。如果底层 Socket 不支持 SO_SNDBUF 选项,setSendBufferSize()方法会抛出SocketException。
7>SO_KEEPALIVE:当SO_KEEPALIVE选项为true时,表示底层的TCP实现会监视该连接是否有效。当连接处于空闲状态(连接的两端没有互相传送数据)超过了2小时时,本地的TCP实现会发送一个数据包给远程的Socket。如果远程Socket没有发回响应,TCP实现就会持续尝试11分钟,直到接收到响应为止。如果在12分钟内未收到响应,TCP实现就会自动关闭本地Socket,断开连接。在不同的网络平台上,TCP实现尝试与远程Socket对话的时限有所差别。SO_KEEPALIVE选项的默认值为false,表示TCP不会监视连接是否有效,不活动的客户端可能会永远存在下去,而不会注意到服务器已经崩溃。
8>OOBINLINE:OOBINLINE的默认值为false,在这种情况下,当接收方收到紧急数据时不作任何处理,直接将其丢弃。如果用户希望发送紧急数据,应该把OOBINLINE设为true。当OOBINLINE为true时,表示支持发送一个字节的TCP紧急数据。Socket类的sendUrgentData(int data)方法用于发送一个字节的TCP紧急数据。
socket.setOOBInline(true);
接收方会把接收到的紧急数据与普通数据放在同样的队列中。值得注意的是,除非使用一些更高层次的协议,否则接收方处理紧急数据的能力有限,当紧急数据到来时,接收方不会得到任何通知,因此接收方很难区分普通数据与紧急数据,只好按照同样的方式处理它们。
9>服务类型:
IP规定了4种服务类型,用来定性地描述服务的质量,Socket 类用 4 个整数表示服务类型:
①低成本(0x02):发送成本低,耗费资源少;
②高可靠性(0x04):保证把数据可靠地送达目的地;
③最高吞吐量(0x08):一次可以接收或发送大批量的数据;
④最小延迟(0x10):传输数据的速度快, 把数据快速送达目的地。
socket.setTrafficClass(0x04);
这4种服务类型还可以进行组合。例如可以同时要求获得高可靠性和最小延迟。
socket.setTrafficClass(0x04|0x10);
10>性能偏好:
默认情况下套接字使用TCP/IP协议。有些实现可能提供与TCP/IP具有不同性能特征的替换协议。此方法允许应用程序在实现从可用协议中作出选择时表达它自己关于应该如何进行折衷的偏好。
socket.setPerformancePreferences(int connectionTime,int latency,int bandwidth);
①connectionTime:表示用最少时间建立连接;
②latency:表示最小延迟;
③bandwidth:表示最高带宽。
setPerformancePreferences()方法用来设定这3项指标之间的相对重要性。可以为这些参数赋予任意的整数,较大的值指示更强的偏好。负值表示的优先级低于正值。例如,如果应用程序相对于低延迟和高带宽更偏好短连接时间,则其可以使用值 (1, 0, 0) 调用此方法。如果应用程序相对于低延迟更偏好高带宽,而相对于短连接时间更偏好低延迟,则其可以使用值 (0, 1, 2) 调用此方法。 另外在连接套接字后调用此方法无效。
六、示例:
1、不使用线程,实现单客户端与服务器端通信。
Server:
import java.io.*;
import java.net.*;
public class Server{
public static void main(String args[]){
try{
System.out.println("正在启动服务器...");
ServerSocket server=new ServerSocket(4000);
System.out.println("已启动服务器,等待客户端请求...");
//一直是阻塞状态,除非客户端连接请求
Socket socket=server.accept();
System.out.println("客户端已连接到本服务器...");
BufferedWriter out=new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
BufferedReader in=new BufferedReader(new InputStreamReader(socket.getInputStream()));
String fromStr=null;
while(!"over".equals(fromStr=in.readLine())){
System.out.println("服务器收到客户端发送的内容是:"+fromStr);
String toStr="$"+fromStr+"$";
System.out.println("服务器返回给客户端的内容是:"+toStr);
out.write(toStr);
//客户端调用readLine()方法必须有换行标识才能不阻塞
out.newLine();
//使用IO缓冲流时如果缓冲没有满则不去写,除非使用flush()方法
out.flush();
}
System.out.println("服务器端读取结束...");
in.close();
out.close();
server.close();
}catch(IOException e){
e.printStackTrace();
}catch(Exception e){
e.printStackTrace();
}
}
}
Client:import java.io.*;
import java.net.*;
public class Client{
public static void main(String args[]){
try{
System.out.println("正在连接服务器...");
Socket client=new Socket("127.0.0.1",4000);
System.out.println("已连接到服务器...");
BufferedReader in0=new BufferedReader(new InputStreamReader(System.in));
BufferedReader in=new BufferedReader(new InputStreamReader(client.getInputStream()));
BufferedWriter out=new BufferedWriter(new OutputStreamWriter(client.getOutputStream()));
String toStr=null;
String fromStr=null;
do{
System.out.println("请输入要发送给服务器的内容:");
toStr = in0.readLine();
System.out.println("客户端输入的内容是:"+toStr);
out.write(toStr);
//服务器端调用readLine()方法必须有换行标识才能不阻塞
out.newLine();
//使用IO缓冲流时如果缓冲没有满则不去写,除非使用flush()方法
out.flush();
if(!"over".equals(toStr)){
fromStr=in.readLine();
System.out.println("接收到服务器发送过来的内容是:"+fromStr);
}
}while(!"over".equals(toStr));
System.out.println("客户端发送结束...");
in0.close();
in.close();
out.close();
client.close();
}
catch(Exception e){
e.printStackTrace();
}
}
}
大多数情况下会有多个客户端向服器发送请求,各个客户端之间互不影响,也就是说当某客户端发生异常或阻塞时不会影响其他客户端向服务端发送消息,因此需要使用多线程。
2、使用原始多线程,实现多个客户端与服务器端通信。
Server:
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.*;
public class Server{
public static ServerSocket server=null;
public static int serverPort = 0;
public static void main(String[] args){
if(args.length == 1) {
serverPort = Integer.parseInt(args[0]);
new Server();
}else{
System.out.println("输入内容格式不正确,即将退出...");
System.exit(1);
}
}
public Server(){
try {
server = new ServerSocket();
System.out.println("正在启动服务器...");
server.bind(new InetSocketAddress(serverPort));
System.out.println("服务器已启动,等待客户端请求...");
while(true){
ServerThread st = new ServerThread(server.accept());
st.start();
}
} catch (IOException e){
e.printStackTrace();
}
}
}
class ServerThread extends Thread{
Socket client=null;
public ServerThread(Socket s){
System.out.println("服务器收到客户端请求...");
this.client = s;
System.out.println("客户端地址:"+client.getInetAddress().getHostAddress() + ":"+ client.getPort());
System.out.println("服务器端已为该客户端分配线程"+this.getName()+"...");
}
public void run(){
System.out.println("线程" + this.getName() + "已启动...");
try {
BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream(),"UTF-8"));
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(client.getOutputStream(),"UTF-8"));
while(true){
String fromStr=in.readLine();
if(!"over".equals(fromStr)){
if(fromStr!=null && !"".equals(fromStr)){
System.out.println("服务器收到客户端发送的内容是:"+fromStr);
String toStr = "$"+fromStr;
System.out.println("服务器向客户端发送的内容是:"+toStr);
out.write(toStr);
out.newLine();
out.flush();
}
}else{
in.close();
out.close();
break;
}
}
}catch (Exception ex) {
ex.printStackTrace();
}finally {
if(client!=null){
try {
client.close();
System.out.println(this.getName() + "已结束");
}catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
Client:
import java.io.*;
import java.net.*;
public class Client{
public static void main(String args[]){
try{
System.out.println("正在连接服务器...");
Socket client=new Socket("127.0.0.1",4000);
System.out.println("已连接到服务器...");
BufferedReader in0=new BufferedReader(new InputStreamReader(System.in,"UTF-8"));
BufferedReader in=new BufferedReader(new InputStreamReader(client.getInputStream(),"UTF-8"));
BufferedWriter out=new BufferedWriter(new OutputStreamWriter(client.getOutputStream(),"UTF-8"));
String toStr=null;
String fromStr=null;
do{
System.out.println("请输入要发送给服务器的内容:");
toStr = in0.readLine();
if(toStr!=null && !"".equals(toStr)){
System.out.println("客户端输入的内容是:"+toStr);
out.write(toStr);
//服务器端调用readLine()方法必须有换行标识才能不阻塞
out.newLine();
//使用IO缓冲流时如果缓冲没有满则不去写,除非使用flush()方法
out.flush();
if(!"over".equals(toStr)){
fromStr=in.readLine();
System.out.println("接收到服务器发送过来的内容是:"+fromStr);
}
}
}while(!"over".equals(toStr));
System.out.println("客户端发送结束...");
in0.close();
in.close();
out.close();
client.close();
}
catch(Exception e){
e.printStackTrace();
}
}
}
使用原始的多线程需要频繁地创建线程,每有一个客户端请求就需要创建一个线程,造成不必要的资源浪费。因此需要使用线程池,可以循环利用线程而不需要频繁创建线程耗费系统资源。
3、使用线程池,实现多个客户端与服务器端通信。
Server:
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Server{
public static ServerSocket server=null;
public static int serverPort = 0;
public static void main(String[] args){
if(args.length == 1) {
serverPort = Integer.parseInt(args[0]);
new Server();
}else{
System.out.println("输入内容格式不正确,即将退出...");
System.exit(1);
}
}
public Server(){
ExecutorService es = Executors.newFixedThreadPool(2);
try {
server = new ServerSocket();
System.out.println("正在启动服务器...");
server.bind(new InetSocketAddress(serverPort));
System.out.println("服务器已启动,等待客户端请求...");
while(true){
ServerThread st = new ServerThread(server.accept());
es.execute(st);
}
} catch (IOException e){
e.printStackTrace();
} finally{
es.shutdown();
}
}
}
class ServerThread extends Thread{
Socket client=null;
public ServerThread(Socket s){
System.out.println("服务器收到客户端请求...");
this.client = s;
System.out.println("客户端地址:"+client.getInetAddress().getHostAddress() + ":"+ client.getPort());
System.out.println("服务器端已为该客户端分配线程"+this.getName()+"...");
}
public void run(){
System.out.println("线程" + this.getName() + "已启动...");
try {
BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream(),"UTF-8"));
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(client.getOutputStream(),"UTF-8"));
while(true){
String fromStr=in.readLine();
if(!"over".equals(fromStr)){
if(fromStr!=null && !"".equals(fromStr)){
System.out.println("服务器收到客户端发送的内容是:"+fromStr);
String toStr = "$"+fromStr;
System.out.println("服务器向客户端发送的内容是:"+toStr);
out.write(toStr);
out.newLine();
out.flush();
}
}else{
in.close();
out.close();
break;
}
}
}catch (Exception ex) {
ex.printStackTrace();
}finally {
if(client!=null){
try {
client.close();
System.out.println(this.getName() + "已结束");
}catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
Client:
import java.io.*;
import java.net.*;
public class Client{
public static void main(String args[]){
try{
System.out.println("正在连接服务器...");
Socket client=new Socket("127.0.0.1",4000);
System.out.println("已连接到服务器...");
BufferedReader in0=new BufferedReader(new InputStreamReader(System.in,"UTF-8"));
BufferedReader in=new BufferedReader(new InputStreamReader(client.getInputStream(),"UTF-8"));
BufferedWriter out=new BufferedWriter(new OutputStreamWriter(client.getOutputStream(),"UTF-8"));
String toStr=null;
String fromStr=null;
do{
System.out.println("请输入要发送给服务器的内容:");
toStr = in0.readLine();
if(toStr!=null && !"".equals(toStr)){
System.out.println("客户端输入的内容是:"+toStr);
out.write(toStr);
//服务器端调用readLine()方法必须有换行标识才能不阻塞
out.newLine();
//使用IO缓冲流时如果缓冲没有满则不去写,除非使用flush()方法
out.flush();
if(!"over".equals(toStr)){
fromStr=in.readLine();
System.out.println("接收到服务器发送过来的内容是:"+fromStr);
}
}
}while(!"over".equals(toStr));
System.out.println("客户端发送结束...");
in0.close();
in.close();
out.close();
client.close();
}
catch(Exception e){
e.printStackTrace();
}
}
}
4、使用线程池和XML报文,实现多个客户端与服务器端通信。
Message:
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
@XmlRootElement
@XmlType(propOrder={"id","name","age"})
class Body{
private String id;
private String name;
private String age;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
}
public class Message {
private String chkStr; //2位(字节)同步校验位
private String msgLen; //4位(字节)报文长度,报文最长可放9999个字节,转为字节数组长度即为9999,否则超长
private String body; //报文头报文体
private String md5; //8位(字节)MD5校验位
public String getChkStr() {
return chkStr;
}
public void setChkStr(String chkStr) {
this.chkStr = chkStr;
}
public String getMsgLen() {
return msgLen;
}
public void setMsgLen(String msgLen) {
this.msgLen = msgLen;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
public String getMd5() {
return md5;
}
public void setMd5(String md5) {
this.md5 = md5;
}
}
MessageUtil:
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.net.Socket;
import java.text.NumberFormat;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
public class MessageUtil{
//bean转化为xml
public static String convertToXml(Object obj){
String result="";
StringWriter sw = new StringWriter();
try {
JAXBContext context = JAXBContext.newInstance(obj.getClass());
Marshaller marshaller = context.createMarshaller();
//字符编码
marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8");
//省略xml头信息<?xml version="1.0"?>
marshaller.setProperty(Marshaller.JAXB_FRAGMENT, false);
//是否格式化xml字符串
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(obj, sw);
result = sw.toString();
System.out.println("报文头报文体字符串为:"+result);
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
//xml转化为bean
public static <T> T converyToJavaBean(String xml, Class<T> c) {
T t = null;
try {
JAXBContext context = JAXBContext.newInstance(c);
Unmarshaller unmarshaller = context.createUnmarshaller();
t = (T) unmarshaller.unmarshal(new StringReader(xml));
} catch (Exception e) {
e.printStackTrace();
}
return t;
}
//根据传入的body和md5字符串组装并返回报文字符串
public static String makeMessageString(String body,String md5) throws Exception{
//报文头报文体字节数组
byte[] bodyBytes = body.getBytes("UTF-8");
//报文头报文体数组长度
int bodyLength = bodyBytes.length;
//报文总长度(String类型存放字母或数字时,每个字符占1个字节)
int messageLength = 2+4+bodyLength+8;
if(messageLength>9999)
throw new Exception("报文总长度超长");
//设置报文总长度值4位
NumberFormat nf = NumberFormat.getNumberInstance();
nf.setMinimumIntegerDigits(4);
nf.setMaximumIntegerDigits(4);
nf.setGroupingUsed(false);
String msgLen = nf.format(messageLength);
//设置同步校验值分别是0x00和0x11
byte[] tmpBytes=new byte[2];
tmpBytes[0]=0x00;
tmpBytes[1]=0x11;
String chkStr = new String(tmpBytes);
return chkStr+msgLen+body+md5;
}
public static String sendMessage(Socket socket,String messages) throws Exception{
OutputStream outputStream= socket.getOutputStream();
InputStream inputStream = socket.getInputStream();
//写数据流发送报文
outputStream.write(messages.getBytes());
outputStream.flush();
//获得服务端返回的数据
byte[] bytes = new byte[9999];
inputStream.read(bytes);
return new String(bytes);
}
}
Server:
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Server{
public static ServerSocket server=null;
public static int serverPort = 4000;
public static void main(String[] args){
new Server();
}
public Server(){
//创建固定大小的线程池
ExecutorService es = Executors.newFixedThreadPool(5);
try {
server = new ServerSocket();
System.out.println("正在启动服务器...");
server.bind(new InetSocketAddress(serverPort));
System.out.println("服务器已启动,等待客户端请求...");
while(true){
Socket socket = server.accept();
ServerThread st = new ServerThread(socket);
es.execute(st);
}
} catch (IOException e){
e.printStackTrace();
} finally{
es.shutdown();
}
}
}
class ServerThread extends Thread{
Socket client=null;
public ServerThread(Socket s){
System.out.println("服务器收到客户端请求...");
this.client = s;
System.out.println("客户端地址:"+client.getInetAddress().getHostAddress() + ":"+ client.getPort());
System.out.println("服务器端已为该客户端分配线程"+this.getName()+"...");
}
public void run(){
System.out.println("线程" + this.getName() + "已启动...");
try {
OutputStream outputStream= client.getOutputStream();
InputStream inputStream = client.getInputStream();
byte[] receiveBytes = new byte[9999];
byte[] bytes = new byte[4];
while(inputStream.read(receiveBytes,0,9999)!=-1){
bytes[0]=receiveBytes[2];
bytes[1]=receiveBytes[3];
bytes[2]=receiveBytes[4];
bytes[3]=receiveBytes[5];
System.out.println("服务器端收到客户端发送的4位(字节)报文长度是:"+new String(bytes));
outputStream.write(receiveBytes);
outputStream.flush();
receiveBytes = new byte[9999];
}
System.out.println("服务器端接收并返回报文结束...");
inputStream.close();
outputStream.close();
}catch (Exception ex) {
ex.printStackTrace();
}finally {
if(client!=null){
try {
client.close();
System.out.println(this.getName() + "已结束");
}catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
Client:
import java.io.*;
import java.net.*;
public class Client{
public static void main(String args[]){
try{
System.out.println("正在连接服务器...");
Socket client=new Socket("127.0.0.1",4000);
System.out.println("已连接到服务器...");
BufferedReader in=new BufferedReader(new InputStreamReader(System.in,"UTF-8"));
Body body = new Body();
String flag = "n";
do{
System.out.println("准备发送报文...");
System.out.println("请输入id:");
body.setId(in.readLine());
System.out.println("请输入name:");
body.setName(in.readLine());
System.out.println("请输入age:");
body.setAge(in.readLine());
String tmpBody = MessageUtil.convertToXml(body);
String tmpMd5 = "B251AB76";
String messages=MessageUtil.makeMessageString(tmpBody, tmpMd5);
String retMessages=MessageUtil.sendMessage(client, messages);
System.out.println("服务器返回的报文是:"+retMessages);
System.out.println("是否继续发送报文?y or n");
flag=in.readLine();
}while("y".equals(flag));
System.out.println("报文发送结束...");
in.close();
client.close();
}
catch(Exception e){
e.printStackTrace();
}
}
}
参考:http://blog.csdn.net/wangxi969696/article/details/7328089
http://www.cnblogs.com/mouseIT/p/4189386.html
相关文章
- 暂无相关文章
用户点评