Socket Programming


最近学完了Golang的基本语法,发现这个gorountine非常的好用啊。想起当时用Python写计网实验2代码,非常的拉跨,打算重写一下。同时也是对学校复古的实验指导书进行一波注解。

1 预备知识

1.1 Socket

Socket工作在TCP/IP协议栈上,一般作为运输层和应用层之间的抽象,可以看成是不同主机间通信的API。Socket的英文意义为插头,十分形象,就像是在两台主机的进程间插插头连线,从而实现进程间的通信。

🤔日常吐槽一下这个Socket的中文翻译“套接字”,完全不知道他想表达什么

Socket由IP地址端口号组成,前者用来唯一确定主机,后者用来唯一确定进程。

常用的 Socket 类型有两种:流式 Socket(SOCK_STREAM)和数据报式 Socket(SOCK_DGRAM)。流式是一种 面向连接的 Socket,针对于面向连接的 TCP 服务应用;数据报式 Socket 是一种无连接的Socket,对应于无连接的 UDP 服务应用。

1.2 TCP

TCP是一种面向连接的、可靠的、基于字节流的运输层通信协议。

🤔数据通信的可靠性,是贯穿整个计算机网络学习中最重要的概念,学习每一个层次我们都要问一下自己,这一层实现可靠传输了吗?TCP的回答是在运输层实现可靠传输;而UDP不保证,需要在应用层保证或者无可靠传输。

1.3 UDP

UDP是一种无连接的、不可靠的、基于数据报的运输层通信协议。

🤔为啥不可靠的还要用呢?因为UDP只管发数据不控制,可以实现快速传输,像即时视频通信就需要这种快速传输。

2 实验手册

2.1 实验目的

  1. 掌握 Socket 编程过程,编写简单的网络应用程序。
  2. 实验环境:Windows 9x/NT/2000/XP/2003;TCP/IP 协议
  3. 编程工具:Java、VC++;C#;VB 任选

🤔07年的手册,非常的Ancient。说是这么说吧,其实大伙还是用Python的。

2.2 实验内容

利用你选择的任何一个编程语言,分别基于TCP和UDP编写一个简单的Client/Server网络应用程序。

客户程序(基本要求):

  • 能通过用户名和口令登录
  • 向服务器发一文本文件或向服务器端发一段文本要求用户在通信完成后可自行退出

服务器程序:

  • 判断用户身份是否合法为合法用户echo数据
  • 要求在服务器控制台显示服务器所处状态信息和用户登录信息服务器程序可控制关闭连接和退出

其他要求:

  • 所用语言和开发工具可自行选择。可用JAVA、VC、VB、Delphi等。
  • 发送数据的文件格式和内容及访问界面可以自行设计,以方便用户、清晰美观为好。
  • 服务器可设计为重复服务器(一个时间只能和一个客户建立连接),也可设计为并发服务器(同一时间可有多个子进程和不同的客户建立连接)

🤔界面设计可以用Qt,比较省心

3 简易TCP-C/S

这里就用TCP来做个例子吧,其实原理很简单,Server一直挂着等待连接请求,Client发请求就行。

3.1 Server-Alpha

直接从网上拉了一个最简单的样例,这里发生ln.Accept()后,就会一直执行下面的循环操作啦,但问题来了,如果有多个客户端同时发送请求会发生什么事?发现代码里只有单线程,conn存储Socket信息,那如果有别的进程发送请求,它就烂了。

package main

import (
	"bufio"
	"fmt"
	"net"
	"strings"
)

func main() {
	fmt.Println("Launching server...")
	// listen on all interfaces
	ln, _ := net.Listen("tcp", ":8081")
	// accept connection on port
	conn, _ := ln.Accept()
	// run loop forever (or until ctrl-c)
	for {
		// will listen for message to process ending in newline (\n)
		message, _ := bufio.NewReader(conn).ReadString('\n')
		// output message received
		fmt.Print("Message Received:", string(message))
		// sample process for string received
		newmessage := strings.ToUpper(message)
		// send new string back to client
		conn.Write([]byte(newmessage + "\n"))
	}
}

3.2 Client-Alpha

客户端没啥好讲的,很简单。

package main

import (
	"bufio"
	"fmt"
	"net"
	"os"
)

func main() {
	// connect to this socket
	conn, _ := net.Dial("tcp", "127.0.0.1:8081")
	for {
		// read in input from stdin
		reader := bufio.NewReader(os.Stdin)
		fmt.Print("Text to send: ")
		text, _ := reader.ReadString('\n')
		// send to socket
		fmt.Fprintf(conn, text+"\n")
		// listen for reply
		message, _ := bufio.NewReader(conn).ReadString('\n')
		fmt.Print("Message from server: " + message)
	}
}

3.3 Server-Final

解决方法很简单,直接开新的线程就可以了,把之前的响应操作写入handleReq(conn net.Conn)

package main

import (
	"bufio"
	"fmt"
	"net"
	"strings"
)

func main() {
	fmt.Println("Launching server...")
	// listen on all interfaces
	ln, _ := net.Listen("tcp", ":8081")
	// accept connection on port
	// run loop forever (or until ctrl-c)
	for {
		conn, err := ln.Accept()
		if err != nil {
			break
		}
		go handleReq(conn) // 启用一个协程处理请求
	}
}

func handleReq(conn net.Conn) {
	for {
		// will listen for message to process ending in newline (\n)
		message, err := bufio.NewReader(conn).ReadString('\n')
		if err != nil {
			return
		}
		// output message received
		fmt.Print("Message Received:", string(message))
		// sample process for string received
		newmessage := strings.ToUpper(message)
		// send new string back to client
		conn.Write([]byte(newmessage + "\n"))
	}
}

3.4 Client-Final

更改好Server端的多线程以后有一个问题,不知道是谁发过来的,可以通过conn信息去查询啊但是也挺麻烦,不如直接让客户端给自己取个名字。

package main

import (
	"bufio"
	"fmt"
	"net"
	"os"
)

func main() {
	// read username
	fmt.Print("What's your name: ")
	var name string
	fmt.Scan(&name)
    // connect to this socket
	conn, _ := net.Dial("tcp", "127.0.0.1:8081")
	reader := bufio.NewReader(os.Stdin)
	reader.ReadString('\n')
	fmt.Println("Connected")
	for {
		// read in input from stdin
		reader := bufio.NewReader(os.Stdin)
		fmt.Print("Text to send: ")
		text, _ := reader.ReadString('\n')
		// send to socket
		if text == "" {
			break
		}
		sendm := fmt.Sprintf("[%v] %v\n", name, text)
		fmt.Fprintf(conn, sendm)
		// listen for reply
		message, _ := bufio.NewReader(conn).ReadString('\n')
		fmt.Print("Message from server: " + message)
	}
}

4 结语

Golanggoroutinenet包非常强大,很多Socket的细节都不需要处理了。

Python的话也挺好,会对于整个Socket通信的建立会有更深的理解。

🤔Pythonthreading库其实和Golanggoroutine相比难度差不多,但是两者效率就8太清楚辽


Author: Luminolt
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint policy. If reproduced, please indicate source Luminolt !
  TOC