제14강 소켓프로그램통한 원격제어 · 2019-08-15 · raspberrypi 소켓프로그래밍...

60
RaspberryPi 소켓프로그래밍 1 제14강 소켓프로그램통한 원격제어 소켓 프로그래밍 TCP 방식의 서버/클라이언트 프로그래밍 TCP 방식에 의한 LED 원격 제어 UDP 방식의 서버/클라이언트 프로그래밍 UDP 방식에 의한 LED 원격 제어 응용과제 참고) 포트 포워딩

Upload: others

Post on 03-Mar-2020

2 views

Category:

Documents


0 download

TRANSCRIPT

RaspberryPi 소켓프로그래밍 1

제14강

소켓프로그램통한 원격제어

소켓 프로그래밍

TCP 방식의 서버/클라이언트 프로그래밍

TCP 방식에 의한 LED 원격 제어

UDP 방식의 서버/클라이언트 프로그래밍

UDP 방식에 의한 LED 원격 제어

응용과제

참고) 포트 포워딩

RaspberryPi 소켓프로그래밍 2

소켓프로그래밍

* TCP/IP 프로토콜 계층 구조

RaspberryPi 소켓프로그래밍 3

소켓프로그래밍

1) TCP 방식 : 연결형, 신뢰성 확보

RaspberryPi 소켓프로그래밍 4

소켓프로그래밍(계속)

2) UDP 방식 : 비연결형, 비신뢰성

RaspberryPi 소켓프로그래밍 5

소켓프로그래밍(계속)

* socket() : 소켓 생성 함수

#include <sys/socket.h>

#include <sys/types.h>

int socket(int domain, int type, int protocol);

domain : AF_INET(address family) or PF_INET(protocol family), ...

type : SOCK_STREAM(연결형,TCP) / SOCK_DGRAM(비연결형,UDP)

protocol : 0 or IPPROTO_TCP / IPPROTO_UDP

예) TCP 소켓

sock = socket(AF_INET, SOCK_STREAM, 0);

예) UDP 소켓

sock = socket(AF_INET, SOCK_DGRAM, 0);

RaspberryPi 소켓프로그래밍 6

소켓프로그래밍(계속)

* 주소정보를 위한 구조체

■ sockaddr 구조체 : 소켓 주소를 표현하는 범용의 구조체

struct sockaddr { sa_family_t sa_family; // 소켓의 프로토콜 주소체계 char sa_data[14]; // 해당 주소체계에서 사용하는 주소정보

}

■ sockaddr_in 구조체

: IPv4 주소체계에서 사용하는 소켓 주소 표현 구조체

struct sockaddr_in { sin_family_t sin_family; // IPv4 주소체계, AF_INET uint16_t sin_port; // 포트 번호, 16bit, Big Endian struct in_addr sin_addr; // IPv4 IP 주소 구조체, 32bit, Big Endian char sin_zero[8]; // sockaddr과 같은 크기위해, 항상 0

}

struct in_addr { uint32_t s_addr; // IPv4 IP 주소, 32bit, Big Endian}

RaspberryPi 소켓프로그래밍 7

소켓프로그래밍(계속)

■ 서버 사용예

struct sockaddr_in server_address;

memset(&server_address, 0, sizeof(server_address));server_address.sin_family =AF_INET;server_address.sin_port =htons(PORT); server_address.sin_addr.s_addr =INADDR_ANY;// htons()(host to network short) : Big Endian방식으로 변환// INADDR_ANY : 실행중인 시스템 IP 주소에 대한 매크로

■ 클라이언트 사용예

struct sockaddr_in client_address;

memset(&client_address, 0, sizeof(client_address));client_address.sin_family =AF_INET;client_address.sin_port =htons(PORT); client_address.sin_addr.s_addr =inet_addr("192.168.0.40"); // 서버IP// inet_addr() : IP 주소의 점십진표기 문자열을 수치화, Big Endian방식

: 범용의 소켓 주소 구조체 sockaddr로 형변환하여 사용함

(struct sockaddr *)&server_addr

RaspberryPi 소켓프로그래밍 8

소켓프로그래밍(계속)

* bind()

: 통신위해 소켓기술자와 소켓주소 구조체 연결

#include <sys/socket.h>

int bind(int sockfd, struct sockaddr *myaddr, int addrlen);

sockfd : socket()이 생성한 소켓 기술자

*myaddr : 소켓 번호와 연결할 소켓 주소 구조체 포인터

addrlen : 소켓 주소 구조체 크기

리턴값 : 정상 0, 에러 -1

예) bind(sockfd, (struct sockaddr *)&server_address,

sizeof(server_address));

// 구조체를 sockaddr 범용 구조체로 형변환

RaspberryPi 소켓프로그래밍 9

소켓프로그래밍(계속)

* listen()

: 클라이언트로부터의 가용한 연결요청 대기큐의 크기 설정

: 대기큐에서의 요청순서에 따라 서비스. 병행처리아님

int listen(int sockfd, int n)

sockfd : socket()이 생성한 소켓 기술자

n : 대기큐의 길이, 클라이언트 연결 요청 최대 수

리턴값 : 성공했을 경우 0, 실패했을 경우 -1을 리턴

예)

listen(sockfd, 2);

: 서버가 최대 2개의 connect() 요청을 대기시킬 수 있으며,

세번째 이후의 connect() 요청은 거절하여 클라이언트가 알게 함

RaspberryPi 소켓프로그래밍 10

소켓프로그래밍(계속)

* accept()

: 클라이언트의 주소정보를 반환하여 통신 채널 생성

int accept(int sockfd, struct sockaddr *addr, int *addrlen)

sockfd : socket()이 생성한 소켓 기술자

addr : 접속을 허가 받은 클라이언트 sockaddr 구조체 포인터

addrlen : 클라이언트의 sockaddr 구조체의 크기를 받을 포인터

리턴값 : 성공시 생성된 클라이언트의 소켓 기술자, 실패시 -1

RaspberryPi 소켓프로그래밍 11

소켓프로그래밍(계속)

* read() / write()

: TCP 방식에서의 송수신

int write(sockfd, char *buf, int bufsize)

int read(sockfd, char *buf, int bufsize)

sockfd : 서버의 경우 accept()에 의해 생성된 소켓 기술자

클라이언트의 경우 자신의 소켓 기술자

buf : 송수신할 데이터 버퍼

bufsize : 버퍼의 크기

리턴값 : 송수신 문자수, 실패시 -1

: UDP 방식에서는

sendto(), recvfrom() 함수 사용

RaspberryPi 소켓프로그래밍 12

소켓프로그래밍(계속)

* connect() : 클라이언트 측에서 연결요청

: 소켓에 클라이언트의 IP주소, 포트번호를 자동 할당하고,

: TCP 방식의 클라이언트가 서버로의 연결 요청하는 함수

: 사전에 연결할 서버의 주소 구조체 초기화 필요

int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);

sockfd : 클라이언트의 소켓

serv_addr : 서버의 주소 정보를 담고 있는 구조체

addr_len : serv_addr 구조체의 길이

RaspberryPi 소켓프로그래밍 13

소켓프로그래밍(계속)

* close()

: TCP방식에서는 미처리된 패킷들을 모두 처리한 후에 소켓을 닫음

예외) 미처리 패킷을 즉시 버리거나, 지정 시간동안 처리위해

setsockopt() 사용

: UDP방식에서는 단순히 소켓을 닫는 작업

close(sockfd);

RaspberryPi 소켓프로그래밍 14

RaspberryPi 소켓프로그래밍 15

TCP 방식

* TCP 방식의 서버/클라이언트 통신절차

: 함수 호출 과정

RaspberryPi 소켓프로그래밍 16

TCP 방식(계속)

* Linux TCP Server 소스

: 서버의 포트번호로 0x5005를 사용

$ nano tcpServer.c

//=======================================// tcpSever.c (Linux)// for Chatting//=======================================#include <stdio.h>#include <string.h>#include <stdlib.h>#include <unistd.h>#include <netinet/in.h>

#define PORTNUM 0x5005 // port#define BUFFSIZE 256

int main(void) {struct sockaddr_in serv_addr, cli_addr;int serv_fd, cli_fd, clilen;char buffer[BUFFSIZE];

RaspberryPi 소켓프로그래밍 17

printf("[TCP server for chatting...]\n");

// 1) create server socketif((serv_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {

perror("ERROR opening socket");exit(1);

}

// 2) setting server socket structurememset((char *) &serv_addr, 0x00, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);serv_addr.sin_port = htons(PORTNUM);

// 3) bind()if(bind(serv_fd, (struct sockaddr *)&serv_addr,

sizeof(serv_addr)) == -1) { perror("ERROR on binding");exit(1);

}

// 4) listen()listen(serv_fd, 5);

loop:

// 5) accept(), blocking...

RaspberryPi 소켓프로그래밍 18

clilen = sizeof(cli_addr);if((cli_fd = accept(serv_fd, (struct sockaddr *)&cli_addr,

&clilen)) == -1) {perror("ERROR on accept");exit(1);

}

// 6) read/write, write(), sending...write(cli_fd, "Welcome to Chat Server.....", BUFFSIZE);

while(1) {memset(buffer, 0x00, sizeof(buffer));

// 6) read/write, read(), receiving...if((read(cli_fd, buffer, BUFFSIZE)) == -1) {

perror("ERROR reading from socket");exit(1);

}printf("[Guest] %s\n", buffer);

if(buffer[0] == 'q') {close(cli_fd);goto loop;

}

memset(buffer, 0x00, sizeof(buffer));printf("[Server] ");fgets(buffer, BUFFSIZE, stdin);

RaspberryPi 소켓프로그래밍 19

// 6) read/write, write(), sending...write(cli_fd, buffer, BUFFSIZE);if(buffer[0] == 'q')

break;}

// 7) close(), close server socker, disconnectionclose(serv_fd);

return 0;}

RaspberryPi 소켓프로그래밍 20

TCP 방식(계속)

* Linux TCP Client 소스

: 서버의 IP 주소를 SERVERIP 매크로에 지정

$ make tcpClient.c

//=======================================// tcpClient.c (Linux)// for Chatting//=======================================#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <netinet/in.h>

#define SERVERIP "192.168.0.40" // taget !!#define PORTNUM 0x5005 // port#define BUFFSIZE 256

int main(void) {int sock_fd;struct sockaddr_in serv_addr;

RaspberryPi 소켓프로그래밍 21

char buffer[BUFFSIZE];

printf("[TCP server for chatting...]\n");

// 1) create client socketif((sock_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {

perror("ERROR opening socket");exit(1);

}

// 2) setting server socket structureserv_addr.sin_family = AF_INET;serv_addr.sin_addr.s_addr = inet_addr(SERVERIP);serv_addr.sin_port = htons(PORTNUM);

// 3) connectif(connect(sock_fd, (struct sockaddr *)&serv_addr,

sizeof(serv_addr)) == -1){perror("ERROR connecting");exit(1);

}

// 4) read/write, read(), receiving...memset(buffer, 0x00, sizeof(buffer));if(read(sock_fd, buffer, BUFFSIZE)== -1){

perror("ERROR reading from socket");exit(1);

}

RaspberryPi 소켓프로그래밍 22

printf("[Server] %s\n", buffer);

while(1) {memset(buffer, 0x00, sizeof(buffer));

printf("[Guest] ");fgets(buffer, BUFFSIZE, stdin);

// 4) read/write, write(), sending...if(write(sock_fd, buffer, strlen(buffer)) == -1) {

perror("ERROR writing to socket");exit(1);

}if(buffer[0] == 'q')

break;

// 4) read/write, read(), receiving...memset(buffer, 0x00, sizeof(buffer));if(read(sock_fd, buffer, BUFFSIZE) == -1) {

perror("ERROR reading from socket");exit(1);

}

printf("[Server] %s\n", buffer);if(buffer[0] == 'q')

break;}

RaspberryPi 소켓프로그래밍 23

// 5) close(), close client socker, disconnectionclose(sock_fd);

return 0;}

RaspberryPi 소켓프로그래밍 24

TCP 방식(계속)

[실습1] TCP 서버/클라이언트 I

: 앞의 서버측 및 클라이언트 측 소스를 활용

: 소스 컴파일

서버 측 $ make tcpServer

클라이언트 측 $ make tcpClient

: 리눅스 시스템간 동작을 확인

서버 측 $ ./tcpServer

클라이언트 측 # ./tcpClient

RaspberryPi 소켓프로그래밍 25

TCP 방식(계속)

* Windows 소켓 프로그래밍

: 소스에는 winsock2.h 파일을 포함

: 윈도우 소켓의 버전과 해당 버전의 라이브러리를 초기화

WSADATA wsaData;

if(WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {perror("ERROR WSAStartup()");exit(1);

}

// MAKEWORD 매크로 함수 사용, 주번호 2, 부번호 2의 소켓 버전 사용

// 해당버전의 윈속 라이브러리를 사용하겠다고 초기화

: 초기화된 라이브러리 해제

WSACleanup(); // 통상 프로그램의 종료 직전에 위치

: 기타 소켓 관련 함수들은 리눅스 환경과 동일

예외!!!) 소켓을 닫을 때 close() 대신 closesocket() 함수를 사용

RaspberryPi 소켓프로그래밍 26

TCP 방식(계속)

* Windows TCP Client 소스

: 범용성을 위해 서버의 IP 주소를 명령행 인자로 전달

: 사용 포트 번호는 0x5005

//=======================================// tcpClientWin.c (Windows)// for Chatting//=======================================#include <stdio.h>#include <string.h>#include <stdlib.h>#include <winsock2.h>

//#define SERVERIP "192.168.0.40" // taget !!#define PORTNUM 0x5005 //#define BUFFSIZE 256

int main(int argc, char *argv[]) {SOCKET sock_fd;SOCKADDR_IN serv_addr;WSADATA wsaData;

RaspberryPi 소켓프로그래밍 27

char buffer[BUFFSIZE];

if(argc != 2) { printf("usage : %s [port]\n", argv[0]);

exit(0);}

printf("[TCP client for chatting...]\n");

if(WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {perror("ERROR WSAStartup()");exit(1);

}

// 1) create client socketif((sock_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {

perror("ERROR opening socket");exit(1);

}

// 2) setting server socket structureserv_addr.sin_family = AF_INET;serv_addr.sin_addr.s_addr = inet_addr(argv[1]); // SERVERIPserv_addr.sin_port = htons(PORTNUM);

// 3) connectif(connect(sock_fd, (SOCKADDR *)&serv_addr,

sizeof(serv_addr)) == -1) {

RaspberryPi 소켓프로그래밍 28

perror("ERROR connecting");exit(1);

}

// 4) read/write, read(), receiving...memset(buffer, 0x00, sizeof(buffer));if(recv(sock_fd, buffer, BUFFSIZE, 0) == -1) {

perror("ERROR reading from socket");exit(1);

} printf("[Server] %s\n", buffer);

while(1) {memset(buffer, 0x00, sizeof(buffer));printf("[Guest] ");fgets(buffer, BUFFSIZE, stdin);

// 4) read/write, write(), sending...if(send(sock_fd, buffer, strlen(buffer), 0) == -1) {

perror("ERROR writing to socket");exit(1);

}if(buffer[0] == 'q')

break;

// 4) read/write, read(), receiving...memset(buffer, 0x00, sizeof(buffer));if(recv(sock_fd, buffer, BUFFSIZE, 0) == -1) {

RaspberryPi 소켓프로그래밍 29

perror("ERROR reading from socket");exit(1);

}

printf("[Server] %s\n", buffer);if(buffer[0] == 'q')

break;}

// 5) close(), close client socker, disconnectionclosesocket(sock_fd);

WSACleanup();

return 0;}

RaspberryPi 소켓프로그래밍 30

TCP 방식(계속)

[실습2] TCP 서버/클라이언트 II

: Visul Studio에서 ( 프로젝트 이름을 tcpClientWin )

: 소스 편집창에 위 소스 복사

: 윈속 프로그램 컴파일시 조치사항

// 윈속(winsock) 라이브러리 ws2_32.lib를 링크토록

-단축키 Alt-F7 키를 눌러

-프로젝트의 속성 페이지 창에서 ( 아래 좌측 그림 )

-좌측화면의 ‘구성속성 – 링커 – 입력’ 클릭

-우측 화면의 ‘추가 종속성’ 항목 클릭

-우측 끝의 역삼각형의 버튼을 클릭

-편집항목을 선택

-다음의 화면(아래 우측 그림)에서 ws2_32.lib를 입력하고

RaspberryPi 소켓프로그래밍 31

확인 버튼 클릭

: 컴파일 ... F7 키 사용

tcpClientWin.exe 실행파일 생성

RaspberryPi 소켓프로그래밍 32

: 리눅스 서버와 Windows 클라이언트 실행

서버 측 $ ./tcpServer_01

클라이언트 측 c:\> tcpClientWin 192.168.0.40

// 명령행에서 서버의 IP 주소 사용

: 동작 결과

- 서버와 클라이언트 간에 교대로 메시지를 송수신 가능

- 접속 종료 시 첫 글자를 q로 시작

RaspberryPi 소켓프로그래밍 33

TCP 방식(계속)

* TCP 서버/클라이언트에 의한 디바이스 원격 제어

RaspberryPi 소켓프로그래밍 34

TCP 방식(계속)

* Linux TCP Server 소스 ( LED 제어용 )

: LED 회로는 wiringPi 핀 번호체계 #1로 연결 구성

: 기존 TCP 서버 소스 + LED 디바이스를 제어(청색부분)

$ nano tcpServer_01.c

//=======================================// tcpSever_01.c (Linux)// Chatting + controlling LED device//=======================================#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <netinet/in.h>

#include <wiringPi.h> //

#define PORTNUM 0x5005 // port##define BUFFSIZE 256

RaspberryPi 소켓프로그래밍 35

#define P_LED 1 // BCM_GPIO #18

int main(void) {struct sockaddr_in serv_addr, cli_addr;int serv_fd, cli_fd, clilen;char buffer[BUFFSIZE];

printf("[TCP server for chatting and controlling LED...]\n");

// 1) create server socketif((serv_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {

perror("ERROR opening socket");exit(1);

}

// 2) setting server socket structurememset((char *) &serv_addr, 0x00, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);serv_addr.sin_port = htons(PORTNUM);

// 3) bind()if(bind(serv_fd, (struct sockaddr *)&serv_addr,

sizeof(serv_addr)) == -1) { perror("ERROR on binding");exit(1);

}

RaspberryPi 소켓프로그래밍 36

// 4) listen()listen(serv_fd, 5);

loop:

// 5) accept(), blocking...clilen = sizeof(cli_addr);if((cli_fd = accept(serv_fd, (struct sockaddr *)&cli_addr,

&clilen)) == -1) {perror("ERROR on accept");exit(1);

}

// 6) read/write, write(), sending...write(cli_fd, "Welcome to Chat Server.....LED control..", BUFFSIZE);

if(wiringPiSetup() == -1) // wiringPi pin #return 1;

pinMode(P_LED, OUTPUT); //

while(1) {// 6) read/write, read(), receiving...memset(buffer, 0x00, sizeof(buffer));if((read(cli_fd, buffer, BUFFSIZE)) == -1){

perror("ERROR reading from socket");exit(1);

}

RaspberryPi 소켓프로그래밍 37

printf("[Guest] %s\n", buffer);

if(buffer[0] == 'q') {close(cli_fd);goto loop;

}else if(buffer[0] == '0') // LED control....

digitalWrite(P_LED, LOW);else if(buffer[0] == '1')

digitalWrite(P_LED, HIGH);

// 6) read/write, write(), sending...memset(buffer, 0x00, sizeof(buffer));printf("[Server] ");fgets(buffer, BUFFSIZE, stdin);write(cli_fd, buffer, BUFFSIZE);if(buffer[0] == 'q')

break;}

// 7) close(), close server socker, disconnectionclose(serv_fd);

return 0;}

: 컴파일 $ make tcpServer_01

RaspberryPi 소켓프로그래밍 38

TCP 방식(계속)

[실습3] TCP 서버/클라이언트에 의한 LED 제어

: 클라이언트는 기존 Linux용, 혹은 Windows용 사용

1) 리눅스 시스템간 동작을 확인

서버 측 $ ./tcpServer_01

클라이언트 측 # ./tcpClient

2) 리눅스 서버와 Windows 클라이언트간 동작을 확인

서버 측 $ ./tcpServer_01

클라이언트 측 c:\> tcpClientWin 192.168.0.40

RaspberryPi 소켓프로그래밍 39

: 동작 결과

- 클라이언트 측에서 전송하는 문자열 중 첫 문자가 0 혹은 1에 따라

- 서버측 LED 디바이스를 OFF 혹은 ON으로 제어

RaspberryPi 소켓프로그래밍 40

RaspberryPi 소켓프로그래밍 41

UDP 방식

* 비연결형, 비신뢰성

: IP 주소, Port 번호를 항시 전달해야 함!!

RaspberryPi 소켓프로그래밍 42

UDP 방식(계속)

* sendto() / recvfrom()

: UDP 방식에서의 송수신 함수

: 매 패킷에 송수신 주소 정보 포함하여

int sendto(int s, char *buf, int length, int flags,

sockaddr* to, int tolen);

int recvfrom(int s, char* buf, int length, int flags,

sockaddr* from, socklen_t *fromlen);

s // 자신의 소켓 기술자

buf // 송수신 버퍼

length // 버퍼의 크기

flags // 보통 0

to // 목적지의 소켓 주소 구조체

tolen // 목적지의 소켓 주소 구조체 크기

from // 발신자의 소켓 주소 구조체

fromlen // 발신자의 소켓 주소 구조체 크기

RaspberryPi 소켓프로그래밍 43

UDP 방식(계속)

* Linux UDP Server 소스

//=======================================// udpSever.c (Linux)// for Chatting//=======================================#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <netinet/in.h>

#define PORTNUM 0x5005#define BUFFSIZE 256

int main(void) {struct sockaddr_in serv_addr, cli_addr;int serv_fd, cli_fd, clilen;char buffer[BUFFSIZE];

printf("[UDP Server for Chatting...\n");

// 1) create server socket

RaspberryPi 소켓프로그래밍 44

if((serv_fd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {perror("ERROR opening socket");exit(1);

}

// 2) setting server socket structurememset((char *) &serv_addr, 0x00, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_addr.s_addr = INADDR_ANY;serv_addr.sin_port = htons(PORTNUM);

// 3) bind()if(bind(serv_fd, (struct sockaddr *) &serv_addr,

sizeof(serv_addr)) == -1) { perror("ERROR on binding");exit(1);

}

while(1) {// 4) recvfrom/sendto, memset(buffer, 0x00, sizeof(buffer));clilen = sizeof(cli_addr);recvfrom(serv_fd, buffer, BUFFSIZE, 0,

(struct sockaddr *)&cli_addr, &clilen);printf("[Guest] %s\n", buffer);if(buffer[0] == 'q') {

close(cli_fd);break;

RaspberryPi 소켓프로그래밍 45

}

// 4) recvfrom/sendto, sending...printf("[Server] ");memset(buffer, 0x00, sizeof(buffer));fgets(buffer, BUFFSIZE, stdin);sendto(serv_fd, buffer, BUFFSIZE, 0,

(struct sockaddr *)&cli_addr, sizeof(cli_addr));if(buffer[0] == 'q')

break;}// 5) close(), close(serv_fd);

return 0;}

RaspberryPi 소켓프로그래밍 46

UDP 방식(계속)

* Linux UDP Client 소스

//=======================================// udpClient.c (Linux)// for Chatting//=======================================#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <netinet/in.h>

#define SERVERIP "192.168.0.40"#define PORTNUM 0x5005#define BUFFSIZE 256

int main(void) {struct sockaddr_in serv_addr;int cli_fd;int servlen;char buffer[BUFFSIZE];

RaspberryPi 소켓프로그래밍 47

printf("[UDP Client for Chatting...\n");

// 1) create socketif((cli_fd = socket(AF_INET, SOCK_DGRAM, 0)) == -1){

perror("ERROR opening socket");exit(1);

}

serv_addr.sin_family = AF_INET;serv_addr.sin_addr.s_addr = inet_addr(SERVERIP);serv_addr.sin_port = htons(PORTNUM);

while(1) {memset(buffer, 0x00, sizeof(buffer));printf("[Guest] ");fgets(buffer, BUFFSIZE, stdin);

// 2) recvfrom/sendto, sending....sendto(cli_fd, buffer, BUFFSIZE, 0,

(struct sockaddr *)&serv_addr, sizeof(serv_addr));if(buffer[0] == 'q')

break;

// 2) recvfrom/sendto, receiving....memset(buffer, 0x00, sizeof(buffer));servlen = sizeof(serv_addr);recvfrom(cli_fd, buffer, BUFFSIZE, 0,

(struct sockaddr *)&serv_addr, &servlen);

RaspberryPi 소켓프로그래밍 48

printf("[Server] %s\n", buffer);if(buffer[0] == 'q')

break;}

// 3) close(), disconnectionclose(cli_fd);

return 0;}

RaspberryPi 소켓프로그래밍 49

UDP 방식(계속)

[실습4] UDP 방식의 서버/클라이언트 I

: 소스 컴파일

$ make udpServer

$ make udpClient

: 리눅스 시스템간 동작을 확인

서버 측 $ ./udpServer

클라이언트 측 # ./udpClient

RaspberryPi 소켓프로그래밍 50

UDP 방식(계속)

* Windows UDP Client 소스

//=======================================// udpClientWin.c (Windows)// for Chatting//=======================================#include <stdio.h>#include <string.h>#include <stdlib.h>#include <winsock2.h>

//#define SERVERIP "192.168.0.40"#define PORTNUM 0x5005#define BUFFSIZE 256

int main(int argc, char *argv[]) {SOCKADDR_IN serv_addr;WSADATA wsaData;SOCKET cli_fd;int servlen;char buffer[BUFFSIZE];

if(argc != 2) {

RaspberryPi 소켓프로그래밍 51

printf("usage : %s [port]\n", argv[0]); exit(0);}

printf("[TCP client for chatting...]\n");

if(WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {perror("ERROR WSAStartup()");exit(1);

}

// 1) create socketif((cli_fd = socket(AF_INET, SOCK_DGRAM, 0)) == -1){

perror("ERROR opening socket");exit(1);

}

serv_addr.sin_family = AF_INET;serv_addr.sin_addr.s_addr = inet_addr(argv[1]); // SERVERIPserv_addr.sin_port = htons(PORTNUM);

while(1) {memset(buffer, 0x00, sizeof(buffer));printf("[Guest] ");fgets(buffer, BUFFSIZE, stdin);

// 2) recvfrom/sendto, sending....sendto(cli_fd, buffer, BUFFSIZE, 0,

RaspberryPi 소켓프로그래밍 52

(struct sockaddr *)&serv_addr, sizeof(serv_addr));if(buffer[0] == 'q')

break;

// 2) recvfrom/sendto, receiving....memset(buffer, 0x00, sizeof(buffer));servlen = sizeof(serv_addr);recvfrom(cli_fd, buffer, BUFFSIZE, 0,

(struct sockaddr *)&serv_addr, &servlen);printf("[Server] %s\n", buffer);if(buffer[0] == 'q')

break;}

// 3) close(), disconnectionclosesocket(cli_fd);

return 0;}

: 컴파일 ... F7 펑션키를 이용

// 실행 파일 udpClientWin.exe이 생성

RaspberryPi 소켓프로그래밍 53

UDP 방식(계속)

[실습5] UDP 방식의 서버/클라이언트 II

: 2) 리눅스 서버와 Windows 클라이언트간 동작을 확인

서버 측 $ ./udpServer

클라이언트 측 c:\> udpClientWin 192.168.0.40

: 동작 결과

- 클라이언트 측에서 전송하는 문자열 중 첫 문자가 0 혹은 1에 따라

- 서버측 LED 디바이스를 OFF 혹은 ON으로 제어

RaspberryPi 소켓프로그래밍 54

UDP 방식(계속)

* Linux UDP Server 소스 ( LED 제어용 )

//=======================================// udpSever_01.c (Linux)// for Chatting + controlling LED//=======================================#include <stdio.h>#include <stdlib.h>

RaspberryPi 소켓프로그래밍 55

#include <string.h>#include <unistd.h>#include <netinet/in.h>

#include <wiringPi.h> //

#define PORTNUM 0x5005#define BUFFSIZE 256

#define P_LED 1 // BCM_GPIO #18

int main(void) {struct sockaddr_in serv_addr, cli_addr;int serv_fd, cli_fd, clilen;char buffer[BUFFSIZE];

printf("[UDP Server for Chatting and controlling LED...\n");

// 1) create server socketif((serv_fd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {

perror("ERROR opening socket");exit(1);

}

// 2) setting server socket structurememset((char *) &serv_addr, 0x00, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_addr.s_addr = INADDR_ANY;

RaspberryPi 소켓프로그래밍 56

serv_addr.sin_port = htons(PORTNUM);

// 3) bind()if(bind(serv_fd, (struct sockaddr *) &serv_addr,

sizeof(serv_addr)) == -1) { perror("ERROR on binding");exit(1);

}

if(wiringPiSetup() == -1) // wiringPi pin #return 1;

pinMode(P_LED, OUTPUT); //

while(1) {// 4) recvfrom/sendto, memset(buffer, 0x00, sizeof(buffer));clilen = sizeof(cli_addr);recvfrom(serv_fd, buffer, BUFFSIZE, 0,

(struct sockaddr *)&cli_addr, &clilen);printf("[Guest] %s\n", buffer);if(buffer[0] == 'q') {

close(cli_fd);break;

}else if(buffer[0] == '0') // LED control....

digitalWrite(P_LED, LOW);else if(buffer[0] == '1')

RaspberryPi 소켓프로그래밍 57

digitalWrite(P_LED, HIGH);

// 4) recvfrom/sendto, sending...printf("[Server] ");memset(buffer, 0x00, sizeof(buffer));fgets(buffer, BUFFSIZE, stdin);sendto(serv_fd, buffer, BUFFSIZE, 0,

(struct sockaddr *)&cli_addr, sizeof(cli_addr));if(buffer[0] == 'q')

break;}// 5) close(), close(serv_fd);

return 0;}

: 소스 컴파일

$ make udpServer_01

RaspberryPi 소켓프로그래밍 58

UDP 방식(계속)

[실습6] UDP 서버/클라이언트에 의한 LED 원격 제어

: 클라이언트는 기존 Linux용, 혹은 Windows용 사용

: 메시지의 첫 문자를 0, 1에 따라 LED 제어

1) 리눅스 시스템간 동작을 확인

서버 측 $ ./udpServer_01

클라이언트 측 # ./udpClient

2) 리눅스 서버와 Windows 클라이언트간 동작을 확인

서버 측 $ ./udpServer_01

클라이언트 측 c:\> udpClientWin 192.168.0.40

RaspberryPi 소켓프로그래밍 59

RaspberryPi 소켓프로그래밍 60

응용과제

[응용1] TCP 방식 디바이스 원격 제어

: mmap() 혹은 커널 모듈을 활용

:

[응용2] UDP 방식 디바이스 원격 제어

: mmap() 혹은 커널 모듈을 활용

: