8채널, 리플렉션, 네트워크 프로그래밍 [Go Language Tutorial]

8채널, 리플렉션, 네트워크 프로그래밍 [Go Language Tutorial]

1 채널

errChan := make(chan err) 파이프 길이를 지정하지 않으면 버퍼링되지 않은 채널이 생성됩니다.

  • 버퍼링되지 않은 채널의 경우 전송 및 수신 작업이 동기식입니다. 보내기 작업은 수신자가 값을 받을 준비가 될 때까지 대기하고 수신 작업은 보낸 사람이 값을 보낼 준비가 될 때까지 기다립니다. 따라서 교착 상태를 방지하려면 버퍼링되지 않은 채널에서 전송 및 수신 작업을 별도의 코루틴에서 수행해야 합니다.

Go에서 채널은 고정 길이이며 일단 생성되면 변경할 수 없습니다. make 함수를 사용하여 채널을 만들 때 선택적으로 채널의 용량(길이)을 지정할 수 있습니다.

채널의 용량을 지정하지 않으면 기본적으로 버퍼링되지 않은 채널, 즉 길이가 0인 채널이 됩니다. 버퍼링되지 않은 채널은 코루틴이 값을 받을 준비가 될 때까지 전송 작업을 차단하고 코루틴이 값을 보낼 준비가 될 때까지 수신 작업을 차단합니다.

버퍼링되지 않은 채널의 경우 전송 및 수신 작업이 동기식입니다. 보내기 작업은 수신자가 값을 받을 준비가 될 때까지 대기하고 수신 작업은 보낸 사람이 값을 보낼 준비가 될 때까지 기다립니다. 따라서 교착 상태를 방지하려면 버퍼링되지 않은 채널에서 전송 및 수신 작업을 별도의 코루틴에서 수행해야 합니다.

버퍼링된 채널의 경우 채널을 만들 때 채널의 용량을 지정할 수 있습니다. 버퍼링된 채널을 사용하면 즉시 차단하지 않고 작업을 보낼 수 있으며 채널의 버퍼가 가득 찬 경우에만 차단됩니다. 마찬가지로 수신 작업에서는 채널의 버퍼가 비어 있는 경우에만 차단됩니다.

채널의 용량은 채널의 버퍼 크기에만 영향을 미치며 채널이 보유할 수 있는 값의 수에는 영향을 미치지 않습니다. 채널의 용량에 관계없이 수신자가 적시에 값을 수신하는 한 채널에 값을 얼마든지 보낼 수 있습니다.

요약하자면, 채널의 용량은 채널이 저장할 수 있는 값의 개수가 아니라 채널의 버퍼 크기를 의미합니다. 버퍼링되지 않은 채널은 용량이 0인 반면 버퍼링된 채널은 0보다 큰 용량을 지정할 수 있습니다.

1.1 개념 및 빠른 시작

channel:管道,主要用于不同goroutine之间的通讯

요구 사항: 이제 1-200에서 각 숫자의 계승을 계산하고 각 숫자의 계승을 맵에 넣습니다. 마지막으로 표시됩니다. 요청은 고루틴을 사용하여 수행됩니다.

아이디어 분석:

  1. goroutine을 사용하여 완료하는 것이 효율적이지만 동시성/병렬 보안 문제가 있습니다.
  2. 이것은 서로 다른 goroutine이 어떻게 통신하는지에 대한 질문을 제기합니다.

여기에 이미지 설명 삽입

package main
import(
	"fmt"
	"time"
)

var (
	myMap = make(map[int]int, 10)
)

//计算n!,将计算结果放入myMap
func test(n int){
    
    
	res := 1
	for i := 1; i <= n; i++ {
    
    
		res *= i
	}
	myMap[n] = res //concurrent map writes?
}

func main(){
    
    
	//开启200个协程
	for i := 1; i <= 200; i++ {
    
    
		go test(i)
	}
	//休眠10s[防止主线程直接跑完,而协程中的任务未完成]
	time.Sleep(time.Second * 10)
	for i, v := range myMap {
    
    
		fmt.Printf("map[%d]=%d\n", i, v)
	}
}

코드를 실행할 때 동시성 문제가 발생합니다.
여기에 이미지 설명 삽입

서로 다른 고루틴 간에 통신하는 방법:

  1. 전역 변수에 대한 뮤텍스
  2. 파이프라인 채널을 사용하여 해결

1에서 200까지 각 숫자의 계승을 계산하고 각 숫자의 계승을 맵에 넣습니다. 마지막으로 표시됩니다. 다음과 같이 개선하십시오.

① 글로벌 변수를 이용한 뮤텍스 잠금[동시성, 병렬성 문제]

  • 전역 변수 m이 추가되지 않으면 리소스 경합 문제가 발생하여 다음과 같은 메시지가 표시됩니다. 동시 맵 쓰기

  • 뮤텍스에 가입

  • 숫자의 계승이 매우 커서 결과가 범위를 벗어납니다. 계승을 sum += i로 변경할 수 있습니다.

package main
import(
	"fmt"
	"time"
	"sync"
)

var (
	myMap = make(map[int]int, 10)
	//声明一个全局的互斥锁
	//lock 是一个全局的互斥锁
	//sync 是包, synchronized 同步
	//Mutex:是互斥
	lock sync.Mutex
)

//计算n!,将计算结果放入myMap
func test(n int){
    
    
	res := 1
	for i := 1; i <= n; i++ {
    
    
		res += int(i)
	}
	//加锁
	lock.Lock()
	myMap[n] = res //concurrent map writes?
	//解锁
	lock.Unlock()
}

func main(){
    
    
	//开启200个协程
	for i := 1; i <= 200; i++ {
    
    
		go test(i)
	}
	//休眠10s[防止主线程直接跑完,而协程中的任务未完成]
	time.Sleep(time.Second * 10)
	for i, v := range myMap {
    
    
		fmt.Printf("map[%d]=%d\n", i, v)
	}
}

여기에 이미지 설명 삽입

② 채널 사용

1. 채널이 필요한 이유
  • 왜 채널이 필요한가요?
  1. 기존에는 고루틴 통신을 해결하기 위해 전역 변수 잠금 동기화를 사용했지만 완벽하지는 않았습니다.
  2. 모든 고루틴이 완료될 때까지 메인 스레드가 대기하는 시간을 결정하기는 어려우므로 여기에서 설정한 10초는 추정치일 뿐입니다.
  3. 메인 스레드가 오랫동안 잠들면 대기 시간이 길어지고, 대기 시간이 짧으면 여전히 작업 상태에 있는 고루틴이 있을 수 있으며 메인 스레드가 종료될 때 소멸됩니다.
  4. 통신은 전역 변수를 잠그고 동기화하여 이루어지며 여러 코루틴을 사용하여 전역 변수를 읽고 쓰지 않습니다.
  5. 위의 모든 분석은 새로운 커뮤니케이션 메커니즘인 채널을 요구하고 있습니다.
2. 기본 소개
  1. channle의 본질은 데이터 구조 - 대기열 [개략도]
  2. 데이터는 선입 선출[FIFO: 선입 선출]입니다.
  3. 스레드 안전성, 여러 고루틴이 액세스할 때 잠글 필요가 없습니다. 즉, 채널 자체가 스레드로부터 안전합니다.
  4. 채널에는 유형이 있으며 문자열 채널은 문자열 유형 데이터만 저장할 수 있습니다.
    여기에 이미지 설명 삽입
3. 채널 선언 및 정의
  • var 변수 이름 chan 데이터 유형
    예:
    var intChan chan int(intChan은 int 데이터를 저장하는 데 사용됨)
    var mapChan chan map[int]string(mapChan은 map[int]문자열 유형을 저장하는 데 사용됨) var perChan chan Person var
    perChan2 chan * 사람
  • 설명:
    채널은 데이터를 쓰려면 채널을 초기화해야 하는 참조 유형입니다
    . 즉, 채널은 make를 입력한 후에만 사용할 수 있으며 intChan은 정수 int만 쓸 수 있습니다.
4. 빠른 시작 및 주의사항

(1) 빠른 시작:

파이프라인 초기화, 파이프라인에 데이터 쓰기, 파이프라인에서 데이터 읽기 및 기본 주의사항

package main
import (
	"fmt"
)

func main(){
    
    
	//1. 创建一个可以存放3个int类型的管道
	var intChan chan int
	intChan = make(chan int, 3)
	//2. 看看intChan是什么
	fmt.Printf("intChan的值=%v intChan本身地址=%p\n", intChan, &intChan)
	//3. 向管道写入数据
	intChan <- 10
	num := 211
	intChan <- num
	intChan <- -50
	//intChan <- 100 //注意:我们在给管道写入数据时,不能超过其容量
	//4. 看看管道的长度和cap(容量)
	fmt.Printf("channel len=%v cap=%v\n", len(intChan), cap(intChan))
	//5. 从管道中读取数据
	var num2 int
	num2 = <- intChan
	fmt.Println("num2=", num2)
	fmt.Printf("channel len=%v cap=%v\n", len(intChan), cap(intChan))

	//6. 在没有使用协程的情况下,如果管道中的数据已经全部取出,再取就会报告deadlock
	num3 := <- intChan
	num4 := <- intChan
	fmt.Printf("num3=%v, num4=%v", num3, num4)
	// num5 := <- intChan
	// fmt.Println("num5=", num5) //fatal error: all goroutines are asleep - deadlock!   
}

여기에 이미지 설명 삽입
(2) 주의가 필요한 사항:

  1. 지정된 데이터 유형만 채널에 저장할 수 있습니다.
  2. 채널의 데이터가 가득 차면 더 이상 넣을 수 없습니다.
  3. 채널에서 데이터를 꺼내면 계속해서 넣을 수 있습니다.
  4. 코루틴을 사용하지 않는 경우 채널 데이터를 가져오고 다시 가져오면 데드락이 보고됨
5. 채널 사용

여기에 이미지 설명 삽입
여기에 이미지 설명 삽입
여기에 이미지 설명 삽입
여기에 이미지 설명 삽입
여기에 이미지 설명 삽입
여기에 이미지 설명 삽입

여기에 이미지 설명 삽입
연습문제:

여기에 이미지 설명 삽입

package main
import (
	"fmt"
	"math/rand"
	"time"
	"strconv"
)

type Person struct {
    
    
	Name string
	Age int
	Address string
}

func main(){
    
    
	var personChan chan Person
	//make给chan开辟空间
	personChan = make(chan Person, 10)
	//取纳秒时间戳作为种子,保证每次的随机种子都不同
	//给rand种种子
	rand.Seed(time.Now().UnixNano())
	for i := 1; i <= 10; i++ {
    
    
		index := rand.Int()
		fmt.Println("index===", index)
		person := Person{
    
    
			Name: "zhangsan" + strconv.Itoa(index),
			Age: i,
			Address: "beijing" + strconv.Itoa(index),
		}
		personChan <- person
	}
	len := len(personChan)
	for i := 0; i < len; i++ {
    
    
		p := <- personChan
		fmt.Println(p)
	}
}
6. 채널 폐쇄 및 순회

(1) 채널 폐쇄

내장 함수 close를 사용하여 채널을 닫습니다. 채널이 닫히면 더 이상 채널에 데이터를 쓸 수 없지만 채널에서 데이터를 읽을 수는 있습니다.

package main
import (
	"fmt"
)

func main(){
    
    
	intChan := make(chan int, 5)
	intChan <- 10
	intChan <- 20
	close(intChan) //close
	//关闭之后不能再向chan写入数据,但是可以读取
	// intChan <- 30 //panic: send on closed channel
	n1 := <- intChan
	fmt.Println("n1=", n1) //n1= 10
}

(2) 채널 순회

채널은 범위 순회를 지원합니다. 두 가지 세부 사항에 주의하십시오.

  1. 트래버스할 때 채널이 닫히지 않으면 교착 상태 오류가 나타납니다.
  2. 순회 시 채널이 닫히면 데이터는 정상적으로 순회되며 순회가 완료된 후 순회가 종료됩니다.
package main
import (
	"fmt"
)

func main(){
    
    
	intChan := make(chan int, 5)
	intChan <- 10
	intChan <- 20
	intChan <- 50
	close(intChan) //close
	//关闭之后不能再向chan写入数据,但是可以读取
	// intChan <- 30 //panic: send on closed channel
	n1 := <- intChan
	fmt.Println("n1=", n1) //n1= 10
	for v := range intChan {
    
    
		fmt.Printf("value=%v\n", v)
	}
}
7. 종합적인 예
  • 요구 사항:
    1-8000까지 계산해야 하는 숫자 중 소수는 무엇입니까?
    고루틴 과 채널 에 대한 지식을 사용하여 완료
  • 분석 아이디어:
    전통적인 방법은 주기를 사용하여 각 숫자가 소수인지 판단하는 것입니다[ok].
    동시/병렬 방식을 사용하여 소수를 세는 작업을 여러(4) 개의 고루틴에 할당하여 완료하고 작업 완료 시간이 짧습니다.
    여기에 이미지 설명 삽입
package main
import (
	"fmt"
	"time"
)

//向intChan放入1-8000个数
func putNum(intChan chan int){
    
    
	for i := 1; i <= 8000; i++ {
    
    
		intChan <- i
	}
	//关闭chan
	close(intChan)
}

//从intChan取出数据,并判断是否是素数,如果是,就放入到primeChan
func primeNum(intChan chan int, primeChan chan int, exitChan chan bool){
    
    
	var flag bool
	for {
    
    
		time.Sleep(time.Millisecond * 10)
		num, ok := <- intChan
		if !ok {
    
     //取不到数据了,就退出
			break
		}
		flag = true //假设是素数
		for i := 2; i < num; i++ {
    
    
			if num % i == 0 {
    
    
				flag = false
				break
			}
		}
		if flag {
    
    
			primeChan <- num
		}
	}
	fmt.Println("有一个primeNum协程因为取不到数据,退出")
	//这里我们还不能关闭primeChan
	//向exitChan写入true
	exitChan <- true
}

func main(){
    
    
	intChan := make(chan int, 1000)
	primeChan := make(chan int, 2000)
	//标识退出的管道
	exitChan := make(chan bool, 4)
	//开启一个协程,向intChan放入1-8000个数
	go putNum(intChan)
	//开启4个协程,从intChan取出数据,并判断是否是素数
	for i := 0; i < 4; i++ {
    
    
		go primeNum(intChan, primeChan, exitChan)
	}
	//主线程进行处理
	go func(){
    
    
		for i:= 0; i < 4; i++{
    
    
			<-exitChan
		}
		//当我们从exitChan取出了4个结果,就可以放心的关闭primeChan
		close(primeChan)
	}()

	//遍历primeChan,把结果取出
	for {
    
    
		res, ok := <- primeChan
		if !ok {
    
    
			break
		}
		//将结果取出
		fmt.Printf("素数=%d\n", res)
	}
}

결과:
여기에 이미지 설명 삽입

8. 읽기 전용, 쓰기 전용 파이프 및 주의 사항
  • 읽기 전용, 쓰기 전용 파이프:
    여기에 이미지 설명 삽입
    여기에 이미지 설명 삽입
  • 지침
  1. 채널은 읽기 전용 또는 쓰기 전용으로 선언될 수 있습니다.
  2. 읽기 전용 및 쓰기 전용 사례
    여기에 이미지 설명 삽입
    3) select를 사용하여 파이프라인에서 데이터를 가져오는 차단 문제를 해결합니다.

용도 선택:

package main
import (
	"fmt"
	"time"
)

func main(){
    
    
	//使用select可以解决管道取数据的阻塞问题
	//1. 定义一个管道 10 int
	intChan := make(chan int, 10)
	for i := 0; i < 10; i++ {
    
    
		intChan <- i
	}
	//2. 定义一个管道 5 string
	stringChan := make(chan string, 5)
	for i := 0; i < 5; i++ {
    
    
		stringChan <- "hello" + fmt.Sprintf("%d", i)
	}
	//传统的方法在遍历管道时,如果不关闭,则会因阻塞导致deadlock
	//可是我们在实际开发中,我们可能不好确定什么时候关闭管道
	//办法:我们可以使用select方式解决
	//label:
	for {
    
    
		select {
    
    
			//注意:这里,如果intChan一直没有关闭,不会一直阻塞而deadlock,会自动到下一个case匹配
			case v := <- intChan:
				fmt.Printf("从intChan读取的数据=%d\n", v)
				time.Sleep(time.Second)
			case v := <- stringChan:
				fmt.Printf("从stringChan读取的数据=%s\n", v)
				time.Sleep(time.Second)
			default:
				fmt.Printf("不玩了,都取不到了【程序员可以在这里加入自己的逻辑】\n")
				time.Sleep(time.Second)
				return
				//break label
		}
	}
}

여기에 이미지 설명 삽입

2 반사

2.1 개념

  1. Reflection은 변수의 타입(type), 카테고리(kind) 등 런타임 시 변수의 다양한 정보를 동적으로 얻을 수 있습니다.
  2. 구조체 변수인 경우 구조체 자체의 정보(구조체의 필드 및 메서드 포함)도 얻을 수 있습니다.
  3. 리플렉션을 통해 변수의 값을 수정하고 관련 메서드를 호출할 수 있습니다.
  4. 리플렉션을 사용하려면 가져오기(“리플렉트”)가 필요합니다.
    여기에 이미지 설명 삽입
    5) 리플렉션 일반적인 애플리케이션 시나리오
    여기에 이미지 설명 삽입

2.2 리플렉션의 중요한 기능

여기에 이미지 설명 삽입

  1. interface{}와 reflect.Value라는 변수는 상호 교환이 가능하며 실제 개발에서 자주 사용됩니다.
    여기에 이미지 설명 삽입

2.3 빠른 시작

(구조 유형, 인터페이스{}, reflect.Value)에 대한 리플렉션의 기본 작업을 시연하는 사례를 작성하세요.

package main
import (
	"reflect"
	"fmt"
)

type Student struct {
    
    
	Name string
	Age int
}

func reflectTest01(b interface{
    
    }){
    
    
	//通过反射获取到传入变量的type、kind值
	//1. 先获取到reflect.Type
	rType := reflect.TypeOf(b)
	fmt.Println("rType=", rType)
	//2. 获取到reflect.Value
	rVal := reflect.ValueOf(b)
	n2 := 2 + rVal.Int()
	fmt.Println("n2=", n2)
	fmt.Printf("rVal=%v rType=%T\n", rVal, rType)
	//下面我们将rVal转成interface{}
	iV := rVal.Interface()
	//将interface{}通过断言转成需要的类型
	num2 := iV.(int)
	fmt.Println("num2=", num2)
}

//对结构体的反射
func reflectTest02(b interface{
    
    }){
    
    
	//通过反射获取到传入的变量的type、kind,值
	//1. 先获取到reflect.Type
	rType := reflect.TypeOf(b)
	fmt.Println("rType=", rType)
	//2. 获取到reflect.Value
	rVal := reflect.ValueOf(b)
	//下面我们将rVal转成interface{}
	iV := rVal.Interface()
	fmt.Printf("iv=%v iv type=%T\b", iV, iV)
	//将interface{}通过断言转成需要的类型
	//这里,我们使用类型断言【同学们可以使用switch的断言形式来更加灵活的判断】
	stu, ok := iV.(Student)
	if ok {
    
    
		fmt.Printf("stu.Name=%v\n", stu.Name)
	}

}

func main(){
    
    
	//1. 基本数据类型 反射
	var num int = 100
	reflectTest01(num)
	//2. 定义一个Student实例
	stu := Student{
    
    
		Name: "tom",
		Age: 20,
	}
	reflectTest02(stu)
}

여기에 이미지 설명 삽입

2.4 반영 내용 및 고려사항

  1. reflect.Value.Kind, 변수의 범주를 가져오고 상수를 반환합니다.
    여기에 이미지 설명 삽입
  2. Type과 Kind의 차이[type이 더 구체적임]
    Type은 type, Kind는 category, Type과 Kind는 같거나 다를 수 있음 예: var num int = 10 The Type of num is int, Kind is a 예를 들어
    var stu Student stu의 Type은 pkg1.Student이고 Kind는 struct입니다.
    여기에 이미지 설명 삽입
  1. 리플렉션을 통해 변수를 수정합니다.SetXxx 메소드를 사용하여 설정할 때 해당 포인터 유형을 통해 완료해야 전달된 변수의 값을 변경할 수 있습니다. Value.Elem() 메서드
    여기에 이미지 설명 삽입
  1. reflect.Value.Elem()을 어떻게 이해해야 합니까?
    여기에 이미지 설명 삽입

2.5 종합 사례

리플렉션을 사용하여 구조의 필드를 순회하고 구조의 메서드를 호출하고 구조 태그의 값을 가져옵니다.

package main
import (
	"reflect"
	"fmt"
)

type Monster struct {
    
    
	Name string `json:"name"`
	Age	int `json:"monster_age"` 
	Score float32 `json:"成绩"`
	Sex	string
}
//方法,返回两数的和
func (s Monster) GetSum(n1, n2 int) int {
    
    
	return n1 + n2
}

//方法,接收四个值,给结构体赋值
func (s Monster) Set(name string, age int, score float32, sex string){
    
    
	s.Name = name
	s.Age = age
	s.Score = score
	s.Sex = sex
}

//方法,显示结构体的值
func (s Monster) Print(){
    
    
	fmt.Println("----start----")
	fmt.Println(s)
	fmt.Println("----end----")
}

func TestStruct(a interface{
    
    }){
    
    
	//获取reflect.Type类型
	typ := reflect.TypeOf(a)
	//获取reflect.Value类型
	val := reflect.ValueOf(a)
	//获取到a对应的类别
	kd := val.Kind()
	//如果传入的不是struct,就退出
	if kd != reflect.Struct {
    
    
		fmt.Println("expect struct")
		return 
	}
	//是结构体,获取该结构体有几个字段
	num := val.NumField()
	fmt.Printf("struct has %d fields\n", num)
	for i := 0; i < num; i++ {
    
    
		fmt.Printf("Field %d值为=%v\n", i, val.Field(i))
		//获取到struct标签,注意需要通过reflect.Type来获取tag标签的值
		tagVal := typ.Field(i).Tag.Get("json") //因为前面定义结构体用到了'json标签'
		//如果该字段有tag标签就显示,否则就不显示
		if tagVal != "" {
    
    
			fmt.Printf("Field %d tag 为=%v\n", i, tagVal)
		}
	}

	//获取到该结构体有多少个方法
	numOfMethod := val.NumMethod()
	fmt.Printf("struct has %d methods\n", numOfMethod)
	//var params []reflect.Value
	//方法的排序默认是按照函数名的排序(ASCII码)
	val.Method(1).Call(nil) //获取到第二个【下标为1】方法,调用它 【传参为空】

	//调用结构体的第1个方法 Method(0)
	var params []reflect.Value //声明了 []reflect.Value()
	params = append(params, reflect.ValueOf(10))
	params = append(params, reflect.ValueOf(40))
	res := val.Method(0).Call(params) //传入的参数是[]reflect.Value,返回[]reflect.Value
	fmt.Println("res=", res[0].Int()) //返回结果,返回的结果是[]reflect.Value
}

func main(){
    
    
	var a Monster = Monster{
    
    
		Name: "黄鼠狼精",
		Age: 400,
		Score: 30.9,
	}
	TestStruct(a)
}

여기에 이미지 설명 삽입

3 네트워크 프로그래밍

3.1 개념 및 사전 지식

  1. TCP 소켓 프로그래밍은 네트워크 프로그래밍의 주류입니다. Tcp 소켓 프로그래밍이라고 하는 이유는 기본 계층이 Tcp/ip 프로토콜을 기반으로 하기 때문입니다.예: QQ 채팅
  2. b/s 구조의 http 프로그래밍의 경우 브라우저를 사용하여 서버에 액세스할 때 http 프로토콜을 사용하며 http의 맨 아래 계층은 여전히 ​​tcp 소켓으로 구현됩니다. 예: Jingdong Mall [go 웹 개발 범주에 속함]
  3. 컴퓨터 간에 서로 통신하려면 네트워크 케이블, 네트워크 카드 또는 무선 네트워크 카드가 필요합니다.

여기에 이미지 설명 삽입
4. 동의
여기에 이미지 설명 삽입

여기에 이미지 설명 삽입
5. 항구

  • 0은 예약된 포트입니다.
  • 1-1024는 잘 알려진 포트라고도 알려진 고정 포트(프로그래머가 사용하지 않음)입니다. 즉, 일부 프로그램에서 고정적으로 사용하며 일반 프로그래머는 사용하지 않습니다.
  • 공통 포트: 22: SSH 원격 로그인 프로토콜 23: telnet 사용 21: ftp 사용
    25: smtp 서비스 사용 80: iis 사용 7: echo 서비스
  • 1025-65535는 동적 포트입니다.
    이 포트는 프로그래머가 사용할 수 있습니다.

注意:

  1. 컴퓨터(특히 서버)에서 가능한 한 적은 수의 포트를 엽니다.

  2. 포트는 하나의 프로그램에서만 모니터링할 수 있습니다.

  3. netstat –an을 사용하면 이 시스템에서 수신 대기 중인 포트를 확인할 수 있습니다.

  4. netstat –anb를 사용하여 수신 포트의 pid를 보고 작업 관리자와 함께 안전하지 않은 포트를 닫을 수 있습니다.

3.2 빠른 시작

  • 서버 측 처리 흐름
  1. 포트 8888에서 수신 대기
  2. 클라이언트의 TCP 연결을 수신하고 클라이언트와 서버 간의 연결을 설정합니다.
  3. 링크 요청을 처리하기 위한 고루틴 생성(일반적으로 클라이언트는 링크를 통해 요청 패킷을 보냅니다.)
  • 고객의 처리 흐름
  1. 서버와의 연결 설정
  2. 요청 데이터 전송[단말기], 서버에서 반환된 결과 데이터 수신
  3. 링크 닫기

①서버 기능 및 코드

기능:

  • 포트 8888에서 수신하고 여러 클라이언트와 링크를 생성할 수 있는 서버 측 프로그램을 작성하십시오.
    링크가 성공한 후 클라이언트는 데이터를 보낼 수 있고 서버는 데이터를 수락하여 터미널에 표시합니다. 텔넷을 사용하여 먼저 테스트하고, 그런 다음 테스트할 클라이언트 프로그램을 작성합니다.

암호:

package main

import (
	"fmt"
	"net"
)

func process(conn net.Conn) {
    
    
	//循环接收客户端发送的数据
	defer conn.Close()
	for {
    
    
		//创建一个新的切片
		buf := make([]byte, 1024)
		//1. 等待客户端通过conn发送消息
		//2. 如果客户端没有write(发送消息),那么协程就阻塞在这里
		fmt.Printf("服务器在等待客户端%s 发送信息\n", conn.RemoteAddr().String())
		n, err := conn.Read(buf) //从conn中读取
		if err != nil {
    
    
			fmt.Printf("客户端退出 err=%v", err)
			return
		}
		//3. 显示客户端给服务端发送的数据(打印在控制台上)
		fmt.Print(string(buf[:n]))
	}
}
func main() {
    
    
	fmt.Println("服务器开始监听...")
	//1. tcp表示使用的网络协议是tcp
	//2. 0.0.0.0:8888表示在本地监听8888端口
	listen, err := net.Listen("tcp", "0.0.0.0:8888")
	if err != nil {
    
    
		fmt.Println("listen err=", err)
		return
	}
	defer listen.Close() //延时关闭listen
	//循环等待客户端来连接服务端
	for {
    
    
		//等待客户端连接
		fmt.Println("等待客户端来连接...")
		conn, err := listen.Accept()
		if err != nil {
    
    
			fmt.Println("Accept() err=", err)
		} else {
    
    
			fmt.Printf("Accept() success con=%v 客户端ip=%v\n", conn, conn.RemoteAddr().String())
		}
		//这里准备起一个协程,为客户端服务
		go process(conn)
	}
}

실행 후 효과:

服务器开始监听...
等待客户端来连接...

②클라이언트 기능 및 코드

  1. 서버의 포트 8888에 연결할 수 있는 클라이언트 프로그램을 작성하십시오.
  2. 클라이언트는 데이터의 단일 행을 보낸 다음 종료할 수 있습니다.
  3. 터미널을 통해 데이터를 입력(한 줄 입력하고 한 줄 보내기)하여 서버로 보낼 수 있음
  4. 프로그램을 종료하려면 터미널에 exit를 입력하십시오.
package main

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

func main() {
    
    
	conn, err := net.Dial("tcp", "192.168.1.100:8888")
	if err != nil {
    
    
		fmt.Println("client dial err=", err)
		return
	}
	//功能一:客户端可以发送单行数据,然后就退出
	reader := bufio.NewReader(os.Stdin) //os.Stdin 表示标准输入:【终端】
	for {
    
    
		//从终端读取一行用户输入,并发送给服务端
		line, err := reader.ReadString('\n')
		if err != nil {
    
    
			fmt.Println("readString err=", err)
		}
		//功能二:当用户输入exit就退出
		line = strings.Trim(line, "\r\n")
		if line == "exit" {
    
    
			fmt.Println("客户端退出...")
			break
		}
		n, err := conn.Write([]byte(line))
		if err != nil {
    
    
			fmt.Println("conn Write err=", err)
		}
		fmt.Printf("客户端发送了 %d字节的数据\n", n)
	}
}

③작용효과

여기에 이미지 설명 삽입

Guess you like

Origin blog.csdn.net/weixin_45565886/article/details/130642640