UNIT 52 :: 압축 사용하기
#compress/gzip 패키지
func NewReader(r io.Reader) (*Reader, error) /*io.Reader 인터페이스로 io.Reader 인터페이스를 따르는 압축 해제 인스턴스 생성*/
func NewWriter(w io.Writer) *Writer //io.Writer 인터페이스로 io.Writer 인터페이스를 따르는 압축 인스턴스 생성
#io/ioutil 패키지
func ReadAll(r io.Reader) ([]byte, error) // io.Reader를 끝(EOF)까지 읽어서 바이트 슬라이스로 리턴
read가 들어가는 건 압축해제에, write가 들어가는 건 압축에 사용된다.
1. 압축
: gzip 알고리즘을 사용하여 데이터를 압축한 뒤 파일로 저장하는 코드
- 문자열은 []byte 형식으로 변환
- close 메서드로 압축 인스턴스를 반드시 닫아줘야 함
2. 압축 해제
: hello.txt.gz의 압축을 해제하는 코드
gzip 알고리즘 외에도 다른 압축 알고리즘들이 존재. (ex. compress/bzip2..)
unit 53 :: 암호화 사용하기
1. 해시 알고리즘
단방향 암호화 알고리즘. 패스워드 저장할 때 사용 ex) MD5, SHA1, SHA256, SHA512.
#crypto/sha512 패키지
func New() hash.Hash //SHA512 해시 인스턴스 생성
func Sum512(data []byte) [Size]byte //Sha512 해시 계산하여 리턴
func (d *digest) Write(p []byte)(nn int, err error) //해시 인스턴스에 데이터 추가
func (d0 *digest) Sum(in []byte) []byte //해시 인스턴스에 저장된 데이터의 SHA512 해시 값 추출
sha512 해시 값을 추출하는 방법.
sha512.sum512 함수에 []byte 형식으로 데이터를 넣어주면 해시 값이 리턴된다. new 함수를 이용하여 새로운 인스턴스를 생성하여 write 함수에 데이터를 넣고 sum 함수로 해시 값을 만들면 된다.
2. AES 대칭키 알고리즘
#crypto/aes 패키지
func NewCipher(key []byte)(cipher.Block, error) //대칭키 암호화 블록 생성
func (c *aesCipher) Encrypt(dst, src []byte) //평문을 AES 알고리즘으로 암호화
func (c *aesCipher) Decrypt(dst, src []byte) //AES 알고리즘으로 암호화된 데이터를 평문으로 복호화
aes 암호화&복호화
- 블록 암호화 알고리즘이므로 키와 암호화할 데이터의 크기가 일정해야 함
- encrypt 함수에 넣으면 암호화, decrypt 함수에 넣으면 복호화
- 데이터를 잘라서 암호화하면 보안에 취약 -> ECB 방식
- 긴 데이터를 안전하게 암호화 -> CBC 방식
2-1. CBC 방식 암호화
#crypto/cipher 패키지
func NewCBCEncrypter(b Block, iv []byte) BlockMode //암호화 블록과 초기화 벡터로 암호화 블록 모드 인스턴스 생성
func (x *cbcEncrypter) CryptBlocks(dst, src[]byte) //암호화 블록 모드 인스턴스로 암호화
func NewCBCDecrypter(b Blcok, iv []byte) BlockMode //암호화 블록과 초기화 벡터로 복호화 블록 모드 인스턴스 생성
func (x *cbcDecrypter) CryptBlocks(dst, src []byte) //복호화 블록 모드 인스턴스로 복호화
#io 패키지
func ReadFull(r Reader, buf []byte)(n int, err error) //io.Reader에서 buf의 길이만큼 데이터를 읽음
너무 길어서 코드만 복붙
package main
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"fmt"
"io"
)
func encrypt(b cipher.Block, plaintext []byte) []byte {
if mod := len(plaintext) % aes.BlockSize; mod != 0 {
padding := make([]byte, aes.BlockSize-mod)
plaintext = append(plaintext, padding...)
}
ciphertext := make([]byte, aes.BlockSize+len(plaintext))
iv := ciphertext[:aes.BlockSize]
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
fmt.Println(err)
return nil
}
mode := cipher.NewCBCEncrypter(b, iv)
mode.CryptBlocks(ciphertext[aes.BlockSize:], plaintext)
return ciphertext
}
func decrypt(b cipher.Block, ciphertext []byte) []byte {
if len(ciphertext)%aes.BlockSize != 0 {
fmt.Println("암호화된 데이터의 길이는 블록 크기의 배수가 되어야 합니다.")
return nil
}
iv := ciphertext[:aes.BlockSize]
ciphertext = ciphertext[aes.BlockSize:]
plaintext := make([]byte, len(ciphertext))
mode := cipher.NewCBCDecrypter(b, iv)
mode.CryptBlocks(plaintext, ciphertext)
return plaintext
}
func main() {
key := "Hello Key 123456" //16바이트
s := `동해 물과 백두산이 마르고 닳도록
하느님이 보우하사 우리나라 만세
무궁화 삼천리 화려강산
대한 사람, 대한으로 길이 보전하세.`
block, err := aes.NewCipher([]byte(key))
if err != nil {
fmt.Println(err)
return
}
ciphertext := encrypt(block, []byte(s))
fmt.Printf("%x\n", ciphertext)
plaintext := decrypt(block, ciphertext)
fmt.Println(string(plaintext))
}
실행 결과
- 블록 암호화는 암호화할 데이터의 길이가 블록 크기의 배수여야 한다.
- 모자라는 부분을 패딩으로 채워줌
- 운용방식 : 초기화 벡터(IV)를 첫 블록에 매치, 각 블록은 이전 블록의 암호화 결과와 XOR -> IV는 매번 다른 값으로 생성해야 함
- 복호화할 때 암호화된 데이터의 길이는 블록 크기의 배수가 되어야 함 -> 길이 검사 필요
- CryptBlocks 함수로 복호화
3. RSA 공개키 알고리즘
대칭키 알고리즘 - 암호 키가 유출되면 암호화된 데이터를 모두 풀 수 있다는 단점
공개키 알고리즘 - 대칭키 알고리즘에 비해 속도가 느려 대칭키 알고리즘의 암호 키만 공개키 알고리즘으로 암호화하여 네트워크로 전송하기도 함
func GenerateKey(random io.Reader, bits int)(priv *PrivateKey, err error) //개인키와 공개키 생성
func EncryptPKCSv15(rand io.Reader, pub *PublicKey, msg []byte) (out []byte, err error) //평문을 공개키로 암호화
func DecryptPKCSv15(rand io.Reader, priv *PrivateKey, ciphertext []byte) (out []byte, err error) //암호화된 데이터를 개인키로 복호화
RSA 암호화&복호화
package main
import (
"crypto/rand"
"crypto/rsa"
"fmt"
)
func main() {
privateKey, err := rsa.GenerateKey(rand.Reader, 2048) //개인키와 공개키 생성
if err != nil {
fmt.Println(err)
return
}
publicKey := &privateKey.PublicKey //개인키 변수 안에 공개키가 들어 있음
s := `동해 물과 백두산이 마르고 닳도록
하느님이 보우하사 우리나라 만세.
무궁화 삼천리 화려강산
대한 사람, 대한으로 길이 보전하세.`
ciphertext, err := rsa.EncryptPKCS1v15(
rand.Reader,
publicKey,
[]byte(s),
)
fmt.Printf("%x\n", ciphertext)
plaintext, err := rsa.DecryptPKCS1v15(
rand.Reader,
privateKey,
ciphertext,
)
fmt.Println(string(plaintext))
}
실행결과
- 암호화할 때는 공개키를 사용, 복호화할 때는 개인키를 사용
- 개인키는 외부에 노출할 일 x, 공개키로는 개인키를 추출하기 어렵기 때문에 안전
RSA 메시지 서명, 인증
#crypto/rsa 패키지
func SignPKCS1v15(rand io.Reader, priv *PrivateKey, hash crypto.Hash, hashed []byte) (s []byte, err error) //개인키로 서명
func VerifyPKCS1v15(pub *PublicKey, hash crypto.Hash, hashed []byte, sig []byte) (err error) //공개키로 서명 검증
#crypto/md5 패키지
func New() hash.Hase //MD5 해시 인스턴스 생성
func (d *digest)Write(p []byte)(nn int, err error) //해시 인스턴스에 데이터 추가
func (d0 *digest) Sum(in []byte) []byte //해시 인스턴스에 저장된 데이터의 MD5 해시 값 추출
package main
import (
"crypto"
"crypto/md5"
"crypto/rand"
"crypto/rsa"
"fmt"
)
func main() {
privateKey, err := rsa.GenerateKey(rand.Reader, 2048) //개인키와 공개키 생성
if err != nil {
fmt.Println(err)
return
}
publicKey := &privateKey.PublicKey //개인키 변수 안에 공개키가 들어 있음
message := "안녕하세요. Go 언어"
hash := md5.New()
hash.Write([]byte(message))
digest := hash.Sum(nil)
var h1 crypto.Hash
signature, err := rsa.SignPKCS1v15(
rand.Reader,
privateKey,
h1,
digest,
)
var h2 crypto.Hash
err = rsa.VerifyPKCS1v15(
publicKey,
h2,
digest,
signature,
)
if err != nil {
fmt.Println("검증 실패")
} else {
fmt.Println("검증 성")
}
}
실행 결과
- rsa.SignPKCS1v15 함수에 rand.Reader, crypto.Hash, 개인키, 해시 값을 넣으면 서명 (h1)
- rsa.VerifyPKCS1v15 함수에 공개키, crypto.Hash, 해시 값, 서명 넣으면 검증 (h2)
- 메시지, 메시지에 대한 해시 값, 서명, 공개키는 모두 공개된 정보
UNIT 54 :: 정렬 활용하기
#sort 패키지
func Sort(data Interface) //데이터를 오름차순으로 정렬
func Reverse(data Interface) Interface //내림차순
type IntSlice []int //int 정렬 인터페이스
type Float64Slice []float64 //float64 정렬 인터페이스
type StringSlice []string //string 정렬 인터페이스
- 자료형에 맞게 정렬 인터페이스를 사용 (sort.IntSlice..)
실행결과
- 기본 자료형 (정수, 실수, 문자열)들은 슬라이스를 바로 넣을 수 있는 함수 제공
-> sort.Ints(a), sort.Float64s(a), sort.Strings(c)
1. 구조체 정렬
정렬 인터페이스의 정의 (srot.Interface)
type Interface interface{
Len() int //데이터의 개수(길이) 구함
Less(i,j int) bool //대소관계 판단
Swap(i,j int) //less 메서드에서 true가 나오면 두 데이터의 위치 바꿈
}
- 구조체가 담긴 슬라이스를 정렬하기 위해서 정렬 인터페이스의 정의가 필요함
- ByScore 구조체에 Students 타입을 포함시켜 기존 메서드를 그대로 따름
- 점수를 기준으로 정렬해야 하기에 Less 메서드를 다시 구현 (상속, 확장)
package main
import (
"fmt"
"sort"
)
type Student struct {
name string
score float32
}
type Students []Student
func (s Students) Len() int {
return len(s) //데이터 길이 구하기
}
func (s Students) Less(i, j int) bool {
return s[i].name < s[j].name //학생 이름순으로 정렬
}
func (s Students) Swap(i, j int) {
s[i], s[j] = s[j], s[i] //두 데이터의 위치를 바꿈
}
type ByScore struct { //점수순 정렬을 위해 구조체 정
Students
}
func (s ByScore) Less(i, j int) bool {
return s.Students[i].score < s.Students[j].score //학생 이름순으로 정렬
}
func main() {
s := Students{
{"maria", 89.3},
{"andrew", 72.6},
{"john", 93.1},
}
sort.Sort(s)
fmt.Println(s)
sort.Sort(sort.Reverse(ByScore{s}))
fmt.Println(s)
}
실행 결과
2. 정렬 키로 정렬
package main
import (
"fmt"
"sort"
)
type Student struct {
name string
score float32
}
type By func(s1, s2 *Student) bool //각 상황별 정렬 함수를 저장할 타입
func (by By) Sort(students []Student) {
sorter := &studentSorter{
students: students,
by: by,
}
sort.Sort(sorter)
}
type studentSorter struct {
students []Student
by func(s1, s2 *Student) bool //각 상황별 정렬 함수
}
func (s *studentSorter) Len() int {
return len(s.students)
}
func (s *studentSorter) Less(i, j int) bool {
return s.by(&s.students[i], &s.students[j])
}
func (s *studentSorter) Swap(i, j int) {
s.students[i], s.students[j] = s.students[j], s.students[i]
}
func main() {
s := []Student{
{"maria", 89.3},
{"andrew", 72.6},
{"john", 93.1},
}
name := func(p1, p2 *Student) bool {
return p1.name < p2.name
}
score := func(p1, p2 *Student) bool {
return p1.score < p2.score
}
reverseScore := func(p1, p2 *Student) bool {
return !score(p1, p2)
}
By(name).Sort(s)
fmt.Println(s)
By(reverseScore).Sort(s)
fmt.Println(s)
}
- By : *Student ㅌ입의 매개변수 s1, s2를 받고 bool를 리턴하는 함수 타입. 실제 함수 x
- name, score, reverseScore : 정렬 키 함수
- 하나의 데이터 타입을 여러 인터페이스로 바꿔가면서 OOP를 구현 (객체 지향 프로그래밍)
실행결과
UNIT 55 :: 컨테이너 사용하기
자료구조 - 연결 리스트, 힙, 링
1. 연결 리스트
#container/list 패키지
func New() *List //연결리스트 생성
func(I *List) PushBack(v interface{}) *Element //연결리스트의 맨 뒤에 데이터 추가
func(I *List) Front() *Element //연결 리스트의 맨 앞 데이터를 가져옴
func(I *List) Back() *Element //연결 리스트의 맨 뒤 데이터를 가져옴
Go 언어의 연결 리스트는 이중 연결 리스트 -> 양방향 순회 가능
실행 화면
연결 리스트 함수 : PushBack, PushFront, PushBackList,...
2. 힙
func Init(h Interface) //힙 초기화
func Push(h Interface, x interface{}) //힙에 데이터 추가
- 최대 힙 : 부모 노드의 값이 자식 노드의 값보다 큰 힙
- 최소 힙 : 부모 노드의 값이 자식 노드의 값보다 작은 힙
- heap.Interface 구현 필수
최소 힙
package main
import (
"container/heap"
"fmt"
)
type MinHeap []int //힙을 int 슬라이스로 정의
func (h MinHeap) Len() int {
return len(h) //슬라이스의 길이를 구함
}
func (h MinHeap) Less(i, j int) bool {
r := h[i] < h[j] //대소관계 판단
fmt.Printf("Less %d < %d %t\n", h[i], h[j], r)
return r
}
func (h MinHeap) Swap(i, j int) {
fmt.Printf("Swap %d %d\n", h[i], h[j])
h[i], h[j] = h[j], h[i] //값의 위치를 바꿈
}
func (h *MinHeap) Push(x interface{}) {
fmt.Println("Push", x)
*h = append(*h, x.(int)) //맨 마지막에 값 추가
}
func (h *MinHeap) Pop() interface{} {
old := *h
n := len(old)
x := old[n-1] //슬라이스의 맨 마지막 값을 가져욤
*h = old[0 : n-1] //맨 마지막 값을 제외한 슬라이스를 다시 저장
return x
}
func main() {
data := new(MinHeap) //힙 생성
heap.Init(data) //힙 초기화
heap.Push(data, 5)
heap.Push(data, 2)
heap.Push(data, 7)
heap.Push(data, 3)
fmt.Println(data, "최솟값 : ", (*data)[0])
}
heap.Push 함수로 값을 넣을 때마다 정렬
3. 링
func New(n int) *Ring //링 생성
func (r *Ring) Do(f func(interface{}))) //링의 모든 노드 순회
func (r *Ring) Move(n int) *Ring //링을 회전시킴. 매개변수로 양수를 넣으면 시계방향 회전
- 원형으로 연결된 이중 연결 리스트.
- 처음과 끝 존재 x
- nil을 가리키는 노드 존재 x'
- 뒤로 가든 앞으로 가든 반드시 r.Next()처럼 현재 위치를 변경해주어야 함
UNIT 56 :: TCP 프로토콜 사용하기
server 코드
#net 패키지
func Listen(net, laddr string) (Listener, error) //프로토콜, IP 주소, 포트 번호를 설정하여 네트워크 연결 대기
func (I *TCPListener) Accept() (Conn, error) //클라이언트가 연결되면 tcp 연결을 리턴
func (I *TCPListener) Close() error //tcp 연결 대기를 닫음
func (c * TCPConn) Read(b []byte) (int, error) //받은 데이터를 읽음
func (c *TCPConn) Write(b []byte) (int,error) //데이터를 보냄
func (c *TCPConn) Close() error //tcp 연결을 닫음
경로 : C:\Users\조수경\hello_project\src\tcpserver
package main
import (
"fmt"
"net"
)
func requestHandler(c net.Conn) {
data := make([]byte, 4096)
for {
n, err := c.Read(data) //클라이언트에서 받은 데이터를 읽음
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(data[:n]))
_, err = c.Write(data[:n]) //클라이언트로 데이터를 보냄
if err != nil {
fmt.Println(err)
return
}
}
}
func main() {
ln, err := net.Listen("tcp", ":8000")
if err != nil {
fmt.Println(err)
return
}
defer ln.Close()
for {
conn, err := ln.Accept()
if err != nil {
fmt.Println(err)
continue
}
defer conn.Close()
go requestHandler(conn)
}
}
- 포트 번호만 설정 : 모든 네트워크 인터페이스의 ip주소에서 연결을 받음
- ip 주소와 함께 설정 : 특정 nic에서만 tcp 연결을 받음
결과 화면
client 코드
func Dial(network, address string) (Conn, error) //프로토콜, IP 주소, 포트 번호를 설정하여 서버에 연결
func (c *TCPConn) Close() error //tcp 연결 닫음
func (c *TCPConn) Read(b []byte)(int,error) //받은 데이터 읽음
func (c *TCPConn) Write(b []byte)(int,error) //데이터 보냄
경로 : C:\Users\조수경\hello_project\src\tcpclient
package main
import (
"fmt"
"net"
"strconv"
"time"
)
func main() {
client, err := net.Dial("tcp", "127.0.0.1:8000")
if err != nil {
fmt.Println(err)
return
}
defer client.Close()
go func(c net.Conn) {
data := make([]byte, 4096)
for {
n, err := c.Read(data)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(data[:n]))
time.Sleep(1 * time.Second)
}
}(client)
go func(c net.Conn) {
i := 0
for {
s := "Hello" + strconv.Itoa(i)
_, err := c.Write([]byte(s)) //서버로 데이터를 보냄
if err != nil {
fmt.Println(err)
return
}
i++
time.Sleep(1 * time.Second)
}
}(client)
fmt.Scanln()
}
각각의 폴더에서 exe 파일을 실행해주면 다음과 같은 결과가 나온다. 통신에 성공한 것을 알 수 있다.
UNIT 57 :: RPC 프로토콜 사용하기
RPC : 원격에서 함수를 실행하는 기술
1. 서버
경로 : C:\Users\조수경\hello_project\src\rpcserver
# net/rpc 패키지
func Register(rcvr interface{})error //rpc로 사용할 함수 등록
rpcserver 코드 : 덧셈 함수 호출, 두 수 더하기
package main
import (
"fmt"
"net"
"net/rpc"
)
type Calc int //rpc 서버에 등록하기 위해 임의의 타입으로 정의
type Args struct {
A, B int
}
type Reply struct {
C int
}
func (c *Calc) Sum(args Args, reply *Reply) error {
reply.C = args.A + args.B //두값을 더하여 리턴값 구조체에 넣어줌
return nil
}
func main() {
rpc.Register(new(Calc)) //calc 타입의 인스턴스 생성, rpc 서버에 등록
ln, err := net.Listen("tcp", ":6000")
if err != nil {
fmt.Println(err)
return
}
defer ln.Close()
for {
conn, err := ln.Accept()
if err != nil {
continue
}
defer conn.Close()
go rpc.ServeConn(conn)
}
}
- rpc 서버에 함수를 등록하려면 구조체나 일반 자료형과 같은 타입에 메서드 형태로 구성되어 있어야 함 ex) Cacl 타입을 int 형으로 정의
- tcp 프로토콜 사용
2. 클라이언트
경로 : C:\Users\조수경\hello_project\src\rpcclient
# net/rpc 패키지
func Dial(network, address string) (*Client,error) //프로토콜, ip주소, 포트 번호를 설정하여 rpc 서버에 연결
func (client *Client) Call(serviceMethod string, args interface{}, reply interface{}) error //rpc 서버의 함수를 호출(동기)
func (client *Client) Go(serviceMethod string, args interface{}, reply interface{},done chan *Call) *Call//rpc 서버의 함수를 고루틴으로 호출(비동기)
client 코드
package main
import (
"fmt"
"net/rpc"
)
type Args struct {
A, B int
}
type Reply struct {
C int
}
func main() {
client, err := rpc.Dial("tcp", "127.0.0.1:6000")
if err != nil {
fmt.Println(err)
return
}
defer client.Close()
//동기 호출
args := &Args{1, 2}
reply := new(Reply)
err = client.Call("Calc.Sum", args, reply)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(reply.C)
//비동기 호출
args.A = 4
args.B = 9
sumCall := client.Go("Calc.Sum", args, reply, nil)
<-sumCall.Done
fmt.Println(reply.C)
}
- rpc 서버의 함수를 호출할 때 사용할 매개변수와 리턴 값 구조체는 서버와 동일해야 함
서버를 실행한 뒤, 클라이언트를 실행하면 더한 값이 나오게 된다.
client 실행 결과
UNIT 58 :: HTTP 서버 사용하기
#net/http 패키지
func ListenAndServe(addr string, handler Handler) error //http 연결을 받고, 요청에 응답
func HandleFunc(pattern string, handler func(ResponseWirter, *Request)) //경로별로 요청을 처리할 핸들러 함수를 등록
func Handle(pattern string, handler Handler) //경로별로 요청을 처리할 핸들러 함수를 등록
func FileServer(root FileSystem) Handler //파일 서버 핸들러
func StripPrefix(prefix string, h Handler) Handler //http 요청 경로에서 특정 문자열을 제거
func NewServeMux() *ServeMux //http 요청 멀티플렉서 인스턴스 생성
1. server 코드
경로 : C:\Users\조수경\hello_project\src\httpserver
package main
import (
"net/http"
)
func main() {
s := "hello, world!"
http.HandleFunc("/", func(res http.ResponseWriter, req *http.Request) {
html := `
<html>
<head>
<title>Hello</title>
<script type="text/javascript" src="/assets/hello.js"></script>
<link href="/assets/hello.css" rel="stylesheet"/>
</head>
<body>
<span class="Hello">` + s + `</span>
</body>
</html>
`
res.Header().Set("Content-Type", "text/html")
res.Write([]byte(html))
})
http.Handle(
"/assets/",
http.StripPrefix( //dir이 지정했기에 url 경로에서 /assets/ 삭제
"/assets/",
http.FileServer(http.Dir("assets")), //assets 디렉터리 지정
),
)
http.ListenAndServe(":8080", nil)
}
- http.HandleFunc : 웹 브라우저가 접속했을 떄의 메서드, 쿠키, 헤더 .. ,
- http.Handle : js, css 파일 연결 (assets 경로)
2. js, css 파일
경로 : C:\Users\조수경\hello_project\src\httpserver\assets
- 창 뜨는 것, 글자 색 (red)
UNIT 59 :: 명령줄 옵션 사용하기
명령줄에서 설정한 옵션을 간단하게 사용 가능
#os 패키지
var Args []string //명령줄에서 설정한 옵션
현재 실행 파일 출력
#flag 패키지
func String(name string, value string, usage string) *string //명령줄 옵션을 받은 뒤 문자열로 저장
func Int(name string, value int, usage string) *int
func Float64(name string, value float64, usage string) *float64
func Bool(name string, value bool, usage string) *bool
func Parse() //명령줄 옵션의 내용을 각 자료형별로 분석
func NFlag() int//명령줄 옵션의 개수를 리턴
var Usage = func() {} //명령줄 옵션의 기본 사용법 출력
flag 사용 예제
- 타입별로 사용할 수 있는 함수를 준비해놓고, Parse 함수를 실행하여 각 옵션 값을 저장시킨다. nflag 함수로 설정된 옵션의 개수를 알 수 있고, 설정된 옵션이 없다면 flag.Usage 함수로 옵션 사용법을 출력한다.
cmd 창을 켜서 명령어를 입력해 빌드시켜준다.
명령어 : go build -o cmdflag
빌드가 됐다면, 옵션을 입력시켜준다. 입력된 옵션이 출력되는 것을 확인할 수 있다.
UNIT 60 :: 에러 처리하기
Println 함수로 출력만 했었지만, 다양한 함수로 에러를 처리하는 방법 존재
#fmt 패키지
func Errorf(format string, a...interface{}) error //형식을 지정하여 error 값을 만듦
#log 패키지
func Fatal(v...interface{}) //에러 로그를 출력하고 프로그램을 완전히 종료
func Panic(v...interface{}) //시간과 에러 문자열을 출력한 뒤 패닉을 발생
func Print(v...interface{}) //시간과 에러 메시지를 출력하며 프로그램을 종료시키지 않음
* 1일 때만 정상 동작인 코드 : log.Fatal
- errorf 함수의 사용법은 printf 함수와 같음
- log.Fatal 함수 : 에러 문자열 출력 후 프로그램 완전 종료
- 에러는 출력되지만, 프로그램은 완전히 종료됨
- 정상 종료 : exit code=1
* 1일 때만 정상 동작인 코드 : log.Panic 함수
- log.Panic 함수 : 시간과 에러 문자열을 출력한 뒤 패닉 발생 (panic 함수 대체 가능)
- 런타임 패닉 발생, 콜스택 출력
* 1일 때만 정상 동작인 코드 : recover
- recover 함수 : loog.Panic, panic 함수에서 설정한 에러 메시지를 리턴, 프로그램을 복구
- 프로그램 종료 x, log.Panic 함수에서 설정한 에러 메시지만 출력됨
* 1일 때만 정상 동작인 코드 : log.Print 함수
- log.Print 함수 : 시간과 에러 메시지 출력. 프로그램 종료 x.
단, 프로그램의 실행이 끝난 다음 로그 출력
1. 에러 타입 만들기
: 에러를 확장하여 좀 더 자세한 에러 내용을 저장하기 위해 사용
package main
import (
"fmt"
"log"
"time"
)
type HelloOneError struct {
time time.Time
value int
}
func (e HelloOneError) Error() string {
return fmt.Sprintf("%v:%d는 1이 아닙니다.", e.time, e.value)
}
func helloOne(n int) (string, error) {
if n == 1 {
s := fmt.Sprintln("Hello", n)
return s, nil
}
return "", HelloOneError{time.Now(), n}
}
func main() {
s, err := helloOne(1)
if err != nil {
log.Fatal(err)
}
fmt.Println(s)
s, err = helloOne(2)
if err != nil {
log.Fatal(err)
}
//런타임 에러 발생
fmt.Println(s)
fmt.Println("Hello, world!")
}
- 에러 타입이 아니더라도 에러 함수 구현하면 에러로 사용가능
- error 함수에서 만든 에러 메시지 출력
'GO' 카테고리의 다른 글
[Go] 채널, 동기화 객체, 리플렉션, 동적 파일 생성, 파일 처리, JSON 문서 (2) | 2020.08.08 |
---|