GO语言基础笔记(七):网络编程

目录

Go语言网络协议基础

协议

实现

跨平台网络抽象

简单代码展示

服务端

客户端

服务端客户端通信实战

[Go Linux服务端](#Go Linux服务端)

[Go Linux客户端](#Go Linux客户端)

[Windows C++ 客户端](#Windows C++ 客户端)

总结


Go语言网络协议基础

在 Go 语言中,net/http 包提供了强大的工具来创建 HTTP 服务器。以下是创建基本服务器的步骤:

net/http 包是 Go 语言用于网络编程,特别是用于构建和处理 HTTP 和 HTTPS 协议的应用程序的标准库。我们来探讨一下它的实现方式和它所遵循的协议。

协议

  1. HTTP(超文本传输协议)net/http 包最主要的功能是支持 HTTP,这是一种应用层协议,用于分布式、协作性和超媒体信息系统。HTTP 是一个无状态的请求-响应协议,通常运行在 TCP/IP 协议之上。

  2. HTTPS(安全的 HTTP) :HTTPS 是 HTTP 的安全版本,它在传输层使用 SSL/TLS 协议来提供加密通信和安全的身份认证。net/http 包通过内置的 crypto/tls 包支持 HTTPS。

实现

  1. HTTP 服务器

    • net/http 包通过提供 http.ListenAndServe 函数来启动 HTTP 服务器。这个函数内部创建了一个 net.Listener,通常是一个 TCP 监听器,用于监听传入的 HTTP 请求。
    • 当接收到 HTTP 请求时,它将请求分派给注册的处理函数(使用 http.HandleFunchttp.Handle 注册)。
    • 这些处理函数接收 http.ResponseWriterhttp.Request 对象,用于构造响应和解析请求。
  2. HTTP 客户端

    • net/http 包提供了一个默认的客户端(http.DefaultClient),该客户端使用 http.Transport 来管理 HTTP 请求的底层细节。
    • http.Transport 管理连接池,处理请求的发送,以及接收响应。
    • 它通过 TCP 连接发送 HTTP 请求,并接收响应。对于 HTTPS,它还负责处理 TLS 握手过程。
  3. Request 和 Response 处理

    • HTTP 请求和响应都被抽象为 http.Requesthttp.Response 类型。
    • 这些类型提供了丰富的方法和字段,用于访问和修改 HTTP 请求和响应的各个部分,如 URL、头部、主体等。
  4. 扩展性和灵活性

    • net/http 包设计灵活,易于扩展。开发者可以自定义处理函数、中间件、客户端的行为等。
    • 它还允许开发者替换或增强底层的传输机制,例如通过实现自定义的 http.RoundTripper

跨平台网络抽象

  1. 网络 I/Onet/http 包的底层网络 I/O 操作(如 TCP 连接)主要依赖于 Go 语言的 net 包。net 包提供了一个平台独立的接口来处理网络 I/O 操作。

  2. Go 调度器:Go 的运行时包括一个高效的调度器,用于调度 Go 程程(goroutines)。这个调度器是独立于操作系统线程的,但会与之交互,以高效地利用多核心处理器。网络 I/O 操作通常在 Go 程程中异步执行。

简单代码展示

服务端

Go 复制代码
import (
    "net/http"
)

//创建处理函数
func handler(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("Hello, 世界"))
}

//注册处理函数
http.HandleFunc("/", handler)

//启动
http.ListenAndServe(":8080", nil)

客户端

Go 复制代码
//发送请求
resp, err := http.Get("http://localhost:8080")
if err != nil {
    // 处理错误
}
defer resp.Body.Close()


//读取响应
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
    // 处理错误
}
fmt.Println(string(body))

服务端客户端通信实战

Go Linux服务端

Go 复制代码
package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"strings"
)

// Message 结构体用于 JSON 响应和请求
type Message struct {
	Text string `json:"text"`
}

func main() {
	http.HandleFunc("/echo", echoHandler) // 处理 /echo 路径
	http.HandleFunc("/post", postHandler) // 处理 /post 路径

	fmt.Println("服务器启动在 http://localhost:8080")
	log.Fatal(http.ListenAndServe(":8080", nil)) // 启动服务器
}

// echoHandler 用于回应客户端发送的消息
func echoHandler(w http.ResponseWriter, r *http.Request) {
	// 只接受 GET 请求
	if r.Method != http.MethodGet {
		http.Error(w, "只支持 GET 请求", http.StatusMethodNotAllowed)
		return
	}

	message := r.URL.Query().Get("message")
	response := Message{Text: message}
	jsonResponse, err := json.Marshal(response)

	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	w.Write(jsonResponse)
}

// postHandler 接受 JSON 数据并返回
func postHandler(w http.ResponseWriter, r *http.Request) {
	// 只接受 POST 请求
	if r.Method != http.MethodPost {
		http.Error(w, "只支持 POST 请求", http.StatusMethodNotAllowed)
		fmt.Println("ERROR 1")
		return
	}

	var message Message
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		http.Error(w, "无法读取 body", http.StatusBadRequest)
		fmt.Println("ERROR 2")
		return
	}
	defer r.Body.Close()
	var cleanedJSON string
	jsonString := string(body)
	if !isValidJSON(jsonString) {
		cleanedJSON = removeInvalidChars(jsonString)

	}

	err = json.Unmarshal([]byte(cleanedJSON), &message)
	if err != nil {
		http.Error(w, "无法解析 JSON", http.StatusBadRequest)
		fmt.Println("ERROR 3")
		return
	}

	jsonResponse, err := json.Marshal(message)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		fmt.Println("ERROR 4")
		return
	}

	w.Header().Set("Content-Type", "application/json")
	w.Write(jsonResponse)
}

func isValidJSON(jsonString string) bool {
	// 检查JSON字符串是否包含特殊字符
	return strings.IndexFunc(jsonString, func(r rune) bool {
		return r < 32 || r >= 127
	}) == -1
}

func removeInvalidChars(jsonString string) string {
	var validChars []rune
	for _, r := range jsonString {
		if r >= 32 && r < 127 {
			validChars = append(validChars, r)
		}
	}
	return string(validChars)
}

解释一下 http.HandleFunc 的工作原理:

  1. 函数签名http.HandleFunc 需要两个参数:一个字符串(表示 URL 路径)和一个处理函数。这个处理函数必须符合特定的签名:它接受一个 http.ResponseWriter 和一个 *http.Request 作为参数。

  2. 函数引用 :在 http.HandleFunc("/echo", echoHandler) 中,echoHandler 是一个函数引用,而不是一个函数调用。这意味着我们传递的是函数本身,而不是执行该函数的结果。

  3. 延迟执行 :当服务器运行并接收到路径为 /echo 的 HTTP 请求时,net/http 包的内部机制会调用 echoHandler 函数,并且为它提供必要的 http.ResponseWriter*http.Request 参数。这是在请求发生时发生的,而不是在设置路由时。

  4. 回调机制 :可以把 echoHandler 理解为一个回调函数。在编程中,回调函数是在特定事件或条件满足时由另一个函数调用的函数。在这种情况下,事件是对 /echo 路径的 HTTP 请求。

因此,当你在 http.HandleFunc 中看到 echoHandler 没有传递参数,这是因为你只是在注册一个当特定 HTTP 请求到来时应该被调用的函数,而实际的参数传递是在请求处理时由 net/http 包自动处理的。

这里我们加入json解析的可能错误处理,处理来自windows客户端的json格式错误问题。然后让windows客户端来访问Linux上go写的服务。

Go Linux客户端

需要配置好服务端设定的IP、端口、访问接口等。还有向URL发字符串时特殊字符例如空格、&、¥、%这些如何处理的问题。

Go 复制代码
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
)

type Message struct {
	Text string `json:"text"`
}

func main() {
	getResponse, err := http.Get("http://localhost:8080/echo?message=Hello%2C%20Go%21")

	if err != nil {
		panic(err)
	}
	defer getResponse.Body.Close()

	body, err := ioutil.ReadAll(getResponse.Body)
	if err != nil {
		panic(err)
	}
	fmt.Println("GET Response:", string(body))

	message := Message{Text: "Hi from Client"}
	jsonRequest, err := json.Marshal(message)
	if err != nil {
		panic(err)
	}

	postResponse, err := http.Post("http://localhost:8080/post", "application/json", bytes.NewBuffer(jsonRequest))
	if err != nil {
		panic(err)
	}
	defer postResponse.Body.Close()

	body, err = ioutil.ReadAll(postResponse.Body)
	if err != nil {
		panic(err)
	}
	fmt.Println("POST Response:", string(body))
}

这里打一个Get请求和一个Post请求,经测试,Linux本地没问题。

Windows C++ 客户端

windows客户端开100线程取while true的访问,看看服务器能否顶住。

cpp 复制代码
#include <windows.h>
#include <vector>
#include <winhttp.h>
#include <iostream>
#include <string>
#include <sstream>
#include <thread>

#pragma comment(lib, "winhttp.lib")

std::string SendRequest(const std::string& serverName, const std::string& apiPath, bool isPost, const std::string& postData = "") {
    std::stringstream responseStream;
    HINTERNET hSession = WinHttpOpen(L"A WinHTTP Example Program/1.0", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);

    if (!hSession) {
        responseStream << "WinHttpOpen failed with error: " << GetLastError();
        return responseStream.str();
    }

    std::wstring wServerName = std::wstring(serverName.begin(), serverName.end());
    std::wstring wApiPath = std::wstring(apiPath.begin(), apiPath.end());
    HINTERNET hConnect = WinHttpConnect(hSession, wServerName.c_str(), 8080, 0);

    if (!hConnect) {
        responseStream << "WinHttpConnect failed with error: " << GetLastError();
        WinHttpCloseHandle(hSession);
        return responseStream.str();
    }

    LPCWSTR method = isPost ? L"POST" : L"GET";
    HINTERNET hRequest = WinHttpOpenRequest(hConnect, method, wApiPath.c_str(), NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, 0);

    if (!hRequest) {
        responseStream << "WinHttpOpenRequest failed with error: " << GetLastError();
        WinHttpCloseHandle(hConnect);
        WinHttpCloseHandle(hSession);
        return responseStream.str();
    }

    BOOL bResults = FALSE;
    std::wstring wPostData = std::wstring(postData.begin(), postData.end());
    if (isPost) {
        bResults = WinHttpSendRequest(hRequest, L"Content-Type: application/json", -1, (LPVOID)wPostData.c_str(), wPostData.size() * sizeof(wchar_t), wPostData.size() * sizeof(wchar_t), 0);
    }
    else {
        bResults = WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0);
    }

    if (!bResults) {
        responseStream << "WinHttpSendRequest failed with error: " << GetLastError();
    }
    else {
        bResults = WinHttpReceiveResponse(hRequest, NULL);

        DWORD dwSize = 0;
        DWORD dwDownloaded = 0;
        LPSTR pszOutBuffer;

        if (bResults) {
            do {
                dwSize = 0;
                if (!WinHttpQueryDataAvailable(hRequest, &dwSize)) {
                    responseStream << "WinHttpQueryDataAvailable failed with error: " << GetLastError();
                    break;
                }

                pszOutBuffer = new char[dwSize + 1];
                if (!pszOutBuffer) {
                    responseStream << "Out of memory";
                    dwSize = 0;
                    break;
                }
                else {
                    ZeroMemory(pszOutBuffer, dwSize + 1);

                    if (!WinHttpReadData(hRequest, (LPVOID)pszOutBuffer, dwSize, &dwDownloaded)) {
                        responseStream << "WinHttpReadData failed with error: " << GetLastError();
                    }
                    else {
                        responseStream.write(pszOutBuffer, dwDownloaded);
                    }

                    delete[] pszOutBuffer;
                }
            } while (dwSize > 0);
        }
    }

    if (hRequest) WinHttpCloseHandle(hRequest);
    if (hConnect) WinHttpCloseHandle(hConnect);
    if (hSession) WinHttpCloseHandle(hSession);

    return responseStream.str();
}

std::string serverName = "192.168.125.104";
std::string getApiPath = "/echo?message=Hello%2C%20Go%21";
std::string postApiPath = "/post";
std::string postData = "{\"text\":\"HifromClient\"}";




void myfunc_work() {
    
    while (1)
    {
        std::string getResponse = SendRequest(serverName, getApiPath, false);
        std::string postResponse = SendRequest(serverName, postApiPath, true, postData);

        std::cout << "GET Response: " << getResponse << std::endl;
        std::cout << "POST Response: " << postResponse << std::endl;
    }
}



int main() {

    
    std::vector<std::thread> thvec;
    for (int i = 0; i < 100; i++)
    {
       
        thvec.push_back(std::thread(myfunc_work));
    }
    for (int i = 0; i < 10; i++)
    {
        thvec[i].join();
    }
 
    return 0;
}

实际上肯定不是100个线程同时访问,我的服务器时intel N100的低功耗芯片,Go语言编写的服务能顶住。没有错误出现,都返回成功了 32G的服务器服务内存占比40左右:

多线程访问图:

Linux服务器cpu占用:

总结

简单的服务端与客户端通信,检验了go语言高并发的恐怖。

相关推荐
速盾cdn几秒前
速盾:vue的cdn是干嘛的?
服务器·前端·网络
安於宿命5 分钟前
【Linux】简易版shell
linux·运维·服务器
丶Darling.8 分钟前
MIT 6.S081 Lab1: Xv6 and Unix utilities翻译
服务器·unix·lab·mit 6.s081·英文翻译中文
黄小耶@16 分钟前
linux常见命令
linux·运维·服务器
粤海科技君1 小时前
如何使用腾讯云GPU云服务器自建一个简单的类似ChatGPT、Kimi的会话机器人
服务器·chatgpt·机器人·腾讯云
傲骄鹿先生1 小时前
阿里云centos7.9服务器磁盘挂载,切换服务路径
服务器·阿里云·磁盘
有谁看见我的剑了?2 小时前
Ubuntu 22.04.5 配置vlan子接口和网桥
服务器·网络·ubuntu
编程一生2 小时前
回调数据丢了?
运维·服务器·前端
weixin_518285053 小时前
深度学习笔记11-神经网络
笔记·深度学习·神经网络
龙鸣丿6 小时前
Linux基础学习笔记
linux·笔记·学习