:网络编程

Posted YangYi215

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了:网络编程相关的知识,希望对你有一定的参考价值。

在TCP/IP协议中,“IP地址+TCP或UDP端口号”唯一标识网络通讯中的一个进程。

因此可以用Socket来描述网络连接的一对一关系。

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

1 TCP的C/S架构

1.1 单服务版本

服务端代码:

package main  
  
import (  
   "fmt"  
   "net")  
  
  
func main()   
   // 创建监控  
   listener, err := net.Listen("tcp", "localhost:8000")  
   if err != nil   
      fmt.Println("listen err:", err)  
      return  
     
  
   defer listener.Close()  // 主协程结束时,关闭listener  
  
   fmt.Println("服务器等待客户端建立连接...")  
  
   // 等待客户端连接请求  
   conn, err := listener.Accept()  
  
   if err != nil   
      fmt.Println("accpet err:", err)  
      return  
     
   defer conn.Close()  // 使用结束,断开与客户端链接  
   fmt.Println("客户端与服务器连接建立成功...")  
  
   // 接收客户端数据  
   buf := make([]byte, 1024)  // 创建1024大小的缓冲区,用于read  
  
   n, err := conn.Read(buf)  // 读取到n个大小的数据  
  
   if err != nil   
      fmt.Println("read err:", err)  
      return  
     
  
   fmt.Println("服务器读到:", string(buf[:n])) // 读多少,打印多少  

如图,在整个通信过程中,服务器端有两个socket参与进来,但用于通信的只有 conn 这个socket。它是由 listener创建的。隶属于服务器端。

客户端代码:

package main  
  
import (  
   "fmt"  
   "net")  
  
func main()   
   // 主动发送连接请求  
   conn, err := net.Dial("tcp", "localhost:8000")  
  
   if err != nil   
      fmt.Println("Dial err", err)  
     
  
   defer conn.Close()  // 结束时,关闭连接  
  
   // 发送数据  
   _, err = conn.Write([]byte("Are u ready?"))  
  
   if err != nil   
      fmt.Println("Write err:", err)  
      return  
     

1.2 并发服务

并发服务端:

Accept()函数的作用是等待客户端的链接,如果客户端没有链接,该方法会阻塞。如果有客户端链接,那么该方法返回一个Socket负责与客户端进行通信。所以,每来一个客户端,该方法就应该返回一个Socket与其通信,因此,可以使用一个死循环,将Accept()调用过程包裹起来。

需要注意的是,实现并发处理多个客户端数据的服务器,就需要针对每一个客户端连接,单独产生一个Socket,并创建一个单独的goroutine与之完成通信。

在判断客户端数据是否为“exit”字符串时,要注意,客户端会自动的多发送2个字符:$“\\r\\n”$(这在windows系统下代表回车、换行)

服务端代码:

package main  
  
import (  
   "fmt"  
   "net"   "strings")  
  
  
func main()   
   // 创建监控  
   listener, err := net.Listen("tcp", "localhost:8000")  
   if err != nil   
      fmt.Println("listen err:", err)  
      return  
     
  
   defer listener.Close()  // 主协程结束时,关闭listener  
  
   for   
      // 等待客户端连接请求  
      conn, err := listener.Accept()  
  
      if err != nil   
         fmt.Println("accpet err:", err)  
         return  
        
  
      // 处理用户请求,新建一个协程  
      go HandleConn(conn)  
     
  
  
// 处理用户请求  
func HandleConn(conn net.Conn)   
   // 函数调用完毕,自动关闭conn  
   defer conn.Close()  
  
   // 获取客户端发过来的网址信息  
   addr := conn.RemoteAddr().String()  
   fmt.Println(addr, "connect successful")  
  
   buf := make([]byte, 2048)  
  
   for   
      // 读取用户数据  
      n, err := conn.Read(buf)  
      if err != nil   
         fmt.Println("err=", err)  
         return  
        
  
      fmt.Printf("[%s]: %s\\n",  addr,  string(buf[:n]))  
      fmt.Println("len = ", len(string(buf[:n])))  
  
      //if string(buf[:n-1]) == "exit" // nc测试,发送时,只有/n  
      if string(buf[:n-2]) == "exit"   
         fmt.Println(addr, "exit")  
         return  
        
  
      // 将数据转化为大写,再给用户发送  
      conn.Write([]byte(strings.ToUpper(string(buf[:n]))))  
     

并发客户端:

客户端不仅需要持续的向服务端发送数据,同时也要接收从服务端返回的数据。因此可将发送和接收放到不同的协程中。

主协程循环接收服务器回发的数据(该数据应已转换为大写),并打印至屏幕;子协程循环从键盘读取用户输入数据,写给服务器。读取键盘输入可使用 os.Stdin.Read(str)。定义切片str,将读到的数据保存至str中。

这样,客户端也实现了多任务。

package main  
  
import (  
   "fmt"  
   "net"   "os")  
  
func main()   
   // 主动发送连接请求  
   conn, err := net.Dial("tcp", "localhost:8000")  
  
   if err != nil   
      fmt.Println("Dial err", err)  
     
  
   defer conn.Close()  // 客户端终止时,关闭于服务器通讯的socket  
  
   // 启动子协程: 接受用户键盘输入发送给服务端  
   go func()   
	  // 创建用于存储用户键盘输入数据的切片缓冲区
      str := make([]byte, 1024)    
  
      for   // 反复读取  
         n, err := os.Stdin.Read(str)  // 获取用户键盘输入(阻塞)  
         if err != nil   
            fmt.Println("os.Stdin.Read err:", err)  
            return  
           
  
         // 从键盘读到的数据,发送给服务端  
         _, err = conn.Write(str[:n])  
         if err != nil   
            fmt.Println("conn.Write err:", err)  
            return  
           
        
   ()  
  
   // 主协程: 接受服务端数据,进行打印输出  
   buf := make([]byte, 1024)  // 定义用于存储服务器回发数据的切片缓冲区  
   for   
      n, err := conn.Read(buf)  // 从通信socket中读数据,存入切片缓冲区(阻塞)  
      if err != nil   
         fmt.Println("conn.Read err:", err)  
         return  
        
  
      fmt.Printf("服务器回发: %s\\n", string(buf[:n]))  
     
  

Linux高级网络编程系列教程

一、网络应用层编程


1、Linux网络编程01——网络协议入门
2、Linux网络编程02——无连接和面向连接的区别
3、Linux网络编程03——字节序和地址转换
4、Linux网络编程04——套接字
5、Linux网络编程05——C/S与B/S架构的区别
6、Linux网络编程06——UDP协议编程
7、Linux网络编程07——广播
8、Linux网络编程08——多播
9、Linux网络编程09——TCP编程之客户端
10、Linux网络编程10——TCP编程之服务器
11、Linux网络编程11——tcp、udp迭代服务器
12、Linux网络编程12——tcp三次握手、四次挥手
13、Linux网络编程13——connect()、listen()和accept()三者之间的关系
14、Linux网络编程14——I/O复用之select详解
15、Linux网络编程15——I/O复用之poll详解
16、Linux网络编程16——I/O复用之epoll详解
17、Linux网络编程17——tcp并发服务器(多进程)
18、Linux网络编程18——tcp并发服务器(多线程)
19、Linux网络编程——tcp高效并发服务器(select实现)
20、Linux网络编程——tcp高效并发服务器(poll实现)
21、Linux网络编程——tcp高效并发服务器(epoll实现)


二、网络底层编程(黑客模式)


1、Linux网络编程1——啥叫原始套接字
2、Linux网络编程2——原始套接字编程
3、Linux网络编程3——原始套接字实例:MAC头分析
4、Linux网络编程4——原始套接字实例:MAC地址扫描器
5、Linux网络编程5——IP数据报格式详解
6、Linux网络编程6——TCP、UDP数据包格式详解
7、Linux网络编程7——原始套接字实例:发送UDP数据包
8、Linux网络编程8——libpcap详解
9、Linux网络编程9——libnet详解

以上是关于:网络编程的主要内容,如果未能解决你的问题,请参考以下文章

unix网络编程 需要买几卷

javaAPI_网络编程基础_网络编程基础1

QT 网络编程问题

读过 Unix网络编程 或者 熟知Unix网络编程的 的进来看一下

Java网络编程中怎样用管道流

求unix网络编程