11 网络编程

计算机网络就是实现了多个计算机互联的系统,相互连接的计算机之间彼此能够进行数据交换。正如城市道路系统总是伴随着城市交通规则来使用的道理,计算机网络总是伴随着计算机网络协议一起使用的。网络协议规定了计算机之间连接的物理、机械(网线与网卡的连接规则)、电气(有效的电平范围) 等特性以及计算机之间的相互寻址规则、数据发送冲突的解决、长的数据如何分段传送与接收等。就像不同的城市可能有不同的交通规则一样,目前的网络协议也有多种,其中,TCP/IP 协议就是一个非常实用的网络协议,它是Internet 所遵循的协议,是一个“既成事实”的标准,已广为人知并且广泛应用在大多
数操作系统上,也可用于大多数局域网和广域网上。网络应用程序,就是在已实现了网络互联的不同的计算机上运行的程序,这些程序相百之间可以交换数据。编写网络应用程序,首先必须明确网络程序所要使用的网络协议,TCP/IP 是网络应用程序的首选协议,大多数网络程序都是以这个协议为基础,本章关于网络程序编写的讲解,都是基于TCP/IP 协议的

要想让网络中的计算机能够互相通信,必须为每台计算机指定一个标识号,通过这个标识号来指定要接收数据的计算机和识别发送数据的计算机,在TCP/IP 协议中,这个标识号就是IP 地址,目前IP 地址在计算机中用4个字节,也就是32位的二进制数来表示,称为IDv4。为了便于记忆和使用,我们通常取用每个字节的十进制数,并且每个字节之间用圆点隔开的文本格式来表示IP 地址,如 192.168.8.1。随着计算机网络规模的不断扩大,用4个字节来表示P地址已越来越不敷使用,人们正在实验和定制使用16个字节表示IP地址的格式,这就是IDv6。由于Ipv6 还没有投入使用,现在网络上用的还都是Ipv4,我们这里的知识也只围绕着 Ipv4 来展开。

在TCPIP 协议栈中,有两个高级协议是网络应用程序编写者应该了解的,它们是“传输控制协议”(Transmission Control Protocol,简称TCP)和“用户数据报协议”(User Datagram
Protocol,简称 UDP)TCP 是面向连接的通信协议,TCP 提供两台计算机之间的可靠无差错的数据传输。应用程序利用TCP 进行通信时,源和目标之间会建立一个虚拟连接。这个连接一旦建立,两台计算机之间就可以把数据当作一个双向字节流进行交换。就像我们打电话一样,互相能听到对方的说话,也知道对方的回应是什么。
UDP 是无连接通信协议,UDP 不保证可靠数据的传输,但能够向若千个目标发送数据接收发自若工个源的数据。简单地说,如果一个主机向另外一台主机发送数据,这一数据就会立即发出,而不管另外一台主机是否已准备接收数据。如果另外一台主机收到了数据它不会确认收到与否。
就像传呼台给用户发信息一样,传呼台并不知道你是否能收到信息(为了避免丢失用户信息,他们常常将一条信息发送两遍)

可以认为 Socket 是应用程序创建的一个港口码头,应用程序只要把装着货物的集装箱(在程序中就是要通过网络发送的数据)放到港口码头上,就算完成了货物的运送,剩下来的工作就由货运公司去处理了(在计算机中由驱动程序来充当货运公司)。对接收方来说,应用程序也要创建的一个港口码头,然后就一直等待到该码头的货物到达,最后从码头上取走货物(发给该应用程序的数据)Socket 在应用程序中创建,通过一种绑定机制与驱动程序建立关系,告诉自己所对应的IP和Port。此后,应用程序送给 Socket 的数据,由 Socket 交给驱动程序向网络上发送出去。计算机从网络上收到与该 Socket 定的 IP+Pot 相关的数据后,由驱动程序交给Socket,应用程序便可从该 Socket 中提取接收到的数据。网络应用程序就是这样通过 Socket进行数据的发送与接收的

用第一个构造函数创建 DatagramSocket 对象,没有指定端口号,系统就会分配一个还没有被其他网络程序所使用的端口号。用第二个构造函数创建 DatagramSocket 对象,可以指定自己想要的端口号。用第三个构造函数创建 DatagramSocket 对象,除了指定自己想要的端口号外,还可以指定相关的 IP 地址,这种情况适用于计算机上有多块网卡和多个 IP的情况,我们可以明确规定我们的数据通过哪块网卡向外发送和接收哪块网卡收到的数据如果在创建 DatagramSocket 对象时,我们没有指定网卡的IP 地址,在发送数据时,底层驱动程序会为我们选择其中一块网卡去发送,在接收数据时,我们会接收到所有网卡收到的与程序端口一致的数据,对于一般只有一块网卡的情况,就不用专门指定了,发送和接收时肯定都是它。其实,对于只有一块网卡的情况,在这里指定了P 地址,反而会给程序带来很大的不方便,因为这个网络程序只能在具有这个IP 地址的计算机上运行,而不能在其他的计算机上运行

如果我们的程序不再使用某个 Socket,我们应该调用 DatagramSocket.close0)方法,关闭这个 Socket,通知驱动程序释放为这个 Socket 所保留的资源,系统就可以将这个 Socket所占用的端口号重新分配给其他程序使用。
在发送数据时,我们用Datagramsend0方法,其完整的格式如下:
public void send(DatagramPacket p) throws IOException
在要接收数据时,我们用Datagram.receive(...)方法,其完整的格式如下
public void receive(DatagramPacket p) throws IOException
Datagramsend0和Datagramreceive0)方法都需要我们传递一个 DatagramPacket 类的实例对象,如果把 DatagramSocket 比作创建的港口码头,那么DatagramPacket 就是我们发送和接收数据的集装箱

用第一个构造函数创建的 DatagramPakcet 对象,只指定了数据包的内存空间和大小相当于只定义了集装箱的大小。用第二个构造函数创建的 DatagramPacket 对象,不仅指定了数据包的内存空间和大小,而且指定了数据包的目标地址和端口。在接收数据时,我们是没法事先就知道哪个地址和端口的 Socket 会发来数据,就像我们要准备一个集装箱去接收发给我们的货物时,是不用标明发货人或是收货人的地址信息的,所以我们应该用第一个构造函数来创建接收数据的 DatagramPakcet 对象。在发送数据时,我们必须指定接收方就像我们要发送数据的集装箱上面必须标明接收人的地址信息Socket 的地址和端口号,样的道理,所以我们应该用第二个构造函数来创建发送数据的 DatagramPakcet 对象

 UDP 数据的发送,类似发送寻呼一样的道理,发送者将数据发送出去就不管了定不可靠的,有可能在发送的过程中发生数据丢失。就像寻呼机必须先处于开机接收状态才能接收寻呼一样的道理,我们要先运行UDP 接收程序,再运行UDP发送程序,UDP 数据包的接收是过期作废的。因此,前面的接收程序要比发送程序早运行才行,你调试成功了吗?当UDP 接收程序运行到 DatagramSocket.receive 方法接收数据时,如果还没有可以接收的数据,在正常情况下,receive 方法将阻塞,一直等到网络上有数据到来,receive 接收该数据并返回。如果网络上没有数据发送过来,receive 方法也没有阻塞,肯定是你前面的程序出现了问题,通常都是使用了一个还在被其他程序占用的端口号,你的 DatagramSocket绑定没有成功。

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.util.Scanner;
import java.io.IOException;

public class ClientDemo {
    public static void main(String[] args) throws IOException {
        Scanner sc = new Scanner(System.in);
        DatagramSocket ds = new DatagramSocket();
        while (true) {
            String s = sc.nextLine();
            if ("886".equals(s)){
                break;
            }
            byte[] bytes = s.getBytes();
            InetAddress address = InetAddress.getByName("192.168.137.1");
            int port=10000;
            DatagramPacket dp = new DatagramPacket(bytes, bytes.length, address, port);
            ds.send(dp);
        }
        ds.close();
    }
}
import java.io.IOException;

import java.net.DatagramPacket;
        import java.net.DatagramSocket;

public class ServerDemo {
    public static void main(String[] args) throws IOException {
        DatagramSocket ds = new DatagramSocket(10000);
        while (true) {
            byte[] bytes = new byte[1024];
            DatagramPacket dp = new DatagramPacket(bytes,bytes.length);
            ds.receive(dp);
            byte[] data = dp.getData();
            int length = dp.getLength();
            System.out.println(new java.lang.String(data,0,length));
        }
    }
}

利用UDP 通信的两个程序是平等的,没有主次之分,两个程序代码可以是完全一样的。利用TCP 协议进行通信的两个应用程序,是有主从之分的,一个称为服务器程序,另外一个称为客户机程序,两者的功能和编写方法大不一样。TCP 服务器程序类似 114 查号台,而TCP 客户机程序类似普通电话。必须先有 114 查号台,普通电话才能拨打 114,在 114查号台那边是先有一个总机,总机专门用来接听拨打进来的电话,并不与外面的电话直接对话,而是将接进来的电话分配到一个空闲的座机上,然后由这个座机去与外面的电话直接对话。总机在没有空闲的座机时,可以让对方排队等候,但等候服务的电话达到一定数量时,总机就会彻底拒绝以后再拨打进来的电话。Java 中提供的 ServerSocket 类用于完成类似114 查号台总机的功能,Socket 类用于完成普通电话和 114 查号台端的座机功能。这个交瓦的过程如图11.5所示。
(1)服务器程序创建一个 ServerSocket,然后调用accept 方法等待客户来连接

(2)客户端程序创建一个 Socket 并请求与服务器建立连接

(3)服务器接收客户的连接请求,并创建一个新的 Socket 与该客户建立专线连接

(4)刚才建立了连接的两个 Socket 在一个单独的线程(由服务器程序创建)上对话

(5)服务器开始等待新的连接请求

编写TCP网络服务器程序时,我们首先要用到javanet.ServerSocket类用以创建服务器Socket,通过查阅JDK 文档资料,看到ServerSocket 类的构造函数有如下几种形式:
public ServerSocket() throws IOException

public ServerSocket(int port) throws IOException

public ServerSocket(int port,int backlog) throws IOException

publicServerSocket(int pcrt,int backlog,InetAddress bindAddr)throws IOException
用第一个构造函数创建ServerSocket 对象,没有与任何端口号绑定,不能被直接使用还要继续调用 bind 方法,才能完成其他构造函数所完成的功能。用第二个构造函数创建 ServerSocket 对象,就可以将这个 ServerSocket 绑定到一个指定的端口上,就像为我们的呼叫中心安排一个电话号码一样,如果在这里指定的端口号为0,系统就会为我们分配一个还没有被其他网络程序所使用的端口号,作为服务器程序,端口号必须事先指定,其他客户才能根据这个号码进行连接,所以将端口号指定为0的情况并不常见。
用第三个构造函数创建 ServerSocket 对象,就是在第二个构造函数的基础上,我们根据 backlog 参数指定在服务器忙时,可以与之保持连接请求的等待客户数量,对于第二个构造函数,没有指定这个参数,则使用默认的数量,大小为 50。
用第四个构造函数创建 ServerSocket 对象,我们除了指定第三个构造函数中的参数外,还可以指定相关的 IP 地址,这种情况适用于计算机上有多块网卡和多个 IP 的情况,我们可以明确规定 ServerSocket 在哪块网卡或IP 地址上等待客户的连接请求,在前面几个构造函数中,都没有指定网卡的 IP 地址,底层驱动程序会为我们选择其中一块网卡或一个 IP地址,显然,对于一般只有一块网卡的情况,我们就不用专门指定IP 地址了。我们在前面的 DatagramSocket 部分就讲过,对于只有一块网卡的情况,在这里指定了IP 地址,反而会给程序带来很大的不方便,你的这个网络程序只能在具有这个 P 地址的计算机上运行,而不能在其他的计算机上运行

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class TcpServer {
	public static void main(String[] args){
	try{
		ServerSocket ss = new ServerSocket(8001);
		Socket s = ss.accept();
		InputStream ips = s.getInputStream();
		OutputStream ops = s.getOutputStream();
		ops.write("welcome to www.it315.org!".getBytes());
//		byte[] buf=new byte[1024];
//		int len=ips.read(buf);
//		System.out.println(new String(buf,0,len));
		BufferedReader br = new BufferedReader(new InputStreamReader(ips));
		System.out.println(br.readLine());
		br.close();
		ips.close();
		ops.close();
		s.close();
	}catch(Exception e){
		e.printStackTrace();
	}
	

	}

}
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.*;

class Servicer implements Runnable{
	Socket s;
	public Servicer(Socket s){
		this.s=s;
	}
	public void run() {
		try{
			InputStream ips = s.getInputStream();
			OutputStream ops = s.getOutputStream();
			
			BufferedReader br = new BufferedReader(new InputStreamReader(ips));
			DataOutputStream dos = new DataOutputStream(ops);
			while(true){
				String strWord = br.readLine();
				if(strWord.equalsIgnoreCase("quit"))
					break;
				String setEcho=(new StringBuffer(strWord).reverse()).toString();
				dos.writeBytes(strWord+"---->"+setEcho+System.getProperty("line.separator"));
			}
			br.close();
			dos.close();
			s.close();
			
		}catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
		}
	}
	
}
class TcpServer {
	public static void main(String[] args) {
		try{
			ServerSocket ss = new ServerSocket(8001);
			while(true){
				Socket s = ss.accept();
				new Thread(new Servicer(s)).start();
			}
		}catch (Exception e) {
			e.printStackTrace();// TODO: handle exception
		}
	}

}
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;

public class TcpClient {
	public static void main(String[] args) {
		try{
			//Socket s = new Socket(InetAddress.getByName("192.168.137.1"),8001);
			if(args.length<2){
				System.out.println("Usage:java TcpClient ServerIp ServerPort");
				return;
			}
			Socket s = new Socket(InetAddress.getByName(args[0]),Integer.parseInt(args[1]));
			InputStream ips = s.getInputStream();
			OutputStream ops = s.getOutputStream();
			BufferedReader brKey = new BufferedReader(new InputStreamReader(System.in));
			DataOutputStream dos = new DataOutputStream(ops);
			BufferedReader brNet = new BufferedReader(new InputStreamReader(ips));
			
			while(true){
				String strWord = brKey.readLine();
				dos.writeBytes(strWord+System.getProperty("line.separator"));
				if(strWord.equalsIgnoreCase("quit"))
					break;
				else
					System.out.println(brNet.readLine());
			}
			dos.close();
			brNet.close();
			brKey.close();
			s.close();
		}catch (Exception e) {
			e.printStackTrace();// TODO: handle exception
		}
	}

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值