基于TCP协议的网络编程学习笔记(1)

来源:互联网 发布:网易邮箱端口号 编辑:程序博客网 时间:2024/06/05 15:28

TCP/IP通信协议是一种可靠的网络协议,它在通信的两端各建立一个Socket ,从而在通信的两端之间形成网络虚拟链路。一旦建立了虚拟的网络链路,两端的程序就可以通过虚拟链路进行通信。 JAVA对基于TCP/IP协议的网络通信提供了良好的封装。java使用Socket对象来代表两端的通信端口,并通过Socket产生IO流来进行网络通信.
(一)阻塞式的Scoket通信
(1)使用ServletSocket创建TCP服务器
  TCP通信的两个实体之间并没有服务器,客户端之分,但那是两个通信实体已经建立虚拟链路之后。。在两个通信实体没有建立虚拟链路之前,必须有一个通信实体先做出“主动姿态”,主动接收来自其它通信实体的连接请求.
  Java中能接收其它通信实体连接请求的类是ServerSocket,ServerSocket对象用于监听来自客户端的Socket连接,如果没有连接,它将一直处于等待状态。
  Socket accept():如果接收到一个客户端的 Socket连接请求,该方法将返回一个与客户端Socket对应的Socket,否则该方法一直处于等待状态,线程也被阻塞。
为了创建ServerSocket对象,ServerSocket类提供了如下几个构造器
ServerSocket(int port)
ServerSocket(int port,int backlog)
ServerSocket(int port,int backlog,InetAddress localAddr)
  当ServerSocket使用完毕,应使用ServerSocket的close()方法来关闭该ServerSocket。通常情况下,服务器不应该只接爱一个客户端请求,而应该不断地接受来自客户端的所有请求,所以java程序通常会通过循环,不断地调用ServerSocket的accept()方法。
如下代码断

 //创建一个ServerSocket,用于监听客户端Socket的连接请求  ServerSocket ss = new ServerSocket(30000);  //采用循环不断接受来自客户端的请求  while (true)  {   //每当接受到客户端Socket的请求,服务器端也对应产生一个Socket   Socket s = ss.accept();   //将Socket对应的输出流包装成PrintStream   PrintStream ps = new PrintStream(s.getOutputStream());   //进行普通IO操作   ps.println("欢迎您跟随阿堂(网络时空)一起来学习Scoket的相关知识");   //关闭输出流,关闭Socket   ps.close();   s.close();  }

(2)使用Socket进行通信
客户端通常使用Socket的构造器来连接到指定服务器,Socket通常可使用如下两个构造器

Socket(InetAddress/String remoteAddress,int port)Socket(InetAddress/String remoteAddress,int port,InetAddress localAddr,int localPort)

Socket socket = new Socket("127.0.0.1" , 30000);

// 下面就可以使用Socket进行通信了
……
  当程序执行上面代码时,该代码将连接到指定ip地址和端口的服务器,让服务器的ServerSocket的accept()方法向下执行,于是服务器和客户端就产生一对互相连接的Socket了
  当客户端,服务器产生了对应的Socket之后,这时候程序就无须区分服务器,客户端了,而是通过各自的Socket进行通信,Socket提供了如下两个方法来获取输入流和输出流
InputStream getInputStream():返回该Socket对象对应的输入流,让程序通过该输入流从Socket中取出数据.
OutputStream getOutputStream():返回该Socket对象对应的输出流,让程序通过该输出流向Socket中输出数据.
如:

import java.net.*;import java.io.*;public class Server{ public static void main(String[] args)  throws IOException {  //创建一个ServerSocket,用于监听客户端Socket的连接请求  ServerSocket ss = new ServerSocket(30000);  //采用循环不断接受来自客户端的请求  while (true)  {   //每当接受到客户端Socket的请求,服务器端也对应产生一个Socket   Socket s = ss.accept();   //将Socket对应的输出流包装成PrintStream   PrintStream ps = new PrintStream(s.getOutputStream());   //进行普通IO操作   ps.println("服务器向你发送信息啦!");   //关闭输出流,关闭Socket   ps.close();   s.close();  } }}

(3)加入多线程
  当我们使用传统的BufferReader的readLine()方法读取数据时,当该方法成功返回之前,线程被阻塞,程序无法继续执行。考虑到这个原因,因此服务器应该为每个Socket单独启动一条线程,每条线程与一个客户端进行通信.
  同样,客户端读取服务器数据时,线程也会被阻塞,所以系统也应该为客户端单独启动一条线程,该客户端线程专门负责读取服务器数据.
  下面的实现一个命令行的C/S聊天室应用,服务器应该包含多条线程,每个Socket对应一条线程,该线程负责读取Socket对应输入流的数据(从客户端发送过来的数据),并将读到的数据向每个 Socket输出流发送一遍(将每一个客户端的数据”广播”给其它客户端),因此需要在服务器端使用List来保存所有的Socket。
服务器端提供了两个类:一个是创建 ServerSocket监听的主类,一个是负责处理每个Scoket通信的线程类
如下所示

import java.net.*;import java.io.*;import java.util.*;public class MyServer{ //定义保存所有Socket的ArrayList public static ArrayList<Socket> socketList = new ArrayList<Socket>();    public static void main(String[] args)  throws IOException    {        ServerSocket ss = new ServerSocket(30000);  while(true)  {   //此行代码会阻塞,将一直等待别人的连接   Socket s = ss.accept();   socketList.add(s);   //每当客户端连接后启动一条ServerThread线程为该客户端服务   new Thread(new ServerThread(s)).start();  }    }}
import java.io.*;import java.net.*;import java.util.*;//负责处理每个线程通信的线程类public class ServerThread implements Runnable{ //定义当前线程所处理的Socket Socket so = null; //该线程所处理的Socket所对应的输入流 BufferedReader br = null; public ServerThread(Socket s)  throws IOException {  this.so = s;  //初始化该Socket对应的输入流  br = new BufferedReader(new InputStreamReader(so.getInputStream())); } public void run() {  System.out.println("----Client :" + s.getPort() + " connected-----");        while (true) {            String line = null;            while ((line = readFromClient()) != null) {                for (Socket socket : MyTCPServer.socketList) {                    if (socket == s) {                        continue;                    }                    try {                        System.out.println("send to socket :" + s.getPort());                        PrintStream ps = new PrintStream(socket.getOutputStream());                        ps.println(line);                    } catch (IOException e) {                        e.printStackTrace();                    }                }            }        } } //定义读取客户端数据的方法 private String readFromClient() {  try  {   return br.readLine();   }  //如果捕捉到异常,表明该Socket对应的客户端已经关闭  catch (IOException e)  {   //删除该Socket。   MyServer.socketList.remove(s);  }  return null; }}

  客户端也同样提供了两个类:一个是负责读取键盘输入的Myclient,并将用户输入的数据写入Socket对应的输出流中,也就是客户端程序的主线程类,一个是负责读取Socket对应输入流中的数据(从服务器发送过来的数据).

import java.net.*;import java.io.*;public class MyClient{    public static void main(String[] args)  throws IOException    {  Socket s = s = new Socket("127.0.0.1" , 30000);  //客户端启动ClientThread线程不断读取来自服务器的数据  new Thread(new ClientThread(s)).start();  //获取该Socket对应的输出流  PrintStream ps = new PrintStream(s.getOutputStream()); System.out.println("input:");            Scanner scanner = new Scanner(System.in);            while (true) {//              System.out.println(scanner.next());                ps.println(s.getLocalPort()+":"+scanner.next());            }    }}
import java.io.*;import java.net.*;import java.util.*;public class ClientThread implements Runnable{ //该线程负责处理的Socket private Socket s; //该现成所处理的Socket所对应的输入流 BufferedReader br = null; public ClientThread(Socket s)  throws IOException {  this.s = s;  br = new BufferedReader(   new InputStreamReader(s.getInputStream())); } public void run() {  System.out.println("----Client : "                + s.getLocalPort() + " start-----");        try {            while (true) {                String line = null;                while ((line = br.readLine()) != null) {                    String[] split = line.split(":");                    String src = split[0];                    String content = split[1];                    System.out.println("receive from " + src + ":" + content);                }            }        } catch (IOException e) {            e.printStackTrace();        } }}
0 0
原创粉丝点击