소켓 주소

|

모든 패킷에는 보내는 주소와 받는 주소가 필요합니다. 특히, 전송 계층의 패킷은 추가로 발신지와 수신지의 포트도 필요합니다. 이러한 정보는 sockaddr 구조체에 정의되어 있습니다.

sockaddr 구조체

MacOS 용 sockaddr

struct sockaddr {
  __uint8_t sa_len;        /* total length */
  sa_family_t sa_family;    /* [XSI] address family */
  char sa_data[14];    /* [XSI] addr value (actually larger) */
};

Cygwin 용 sockaddr

struct sockaddr {
  sa_family_t sa_family;    /* address family, AF_xxx	*/
  char sa_data[14];    /* 14 bytes of protocol address	*/
};

sa_family는 주소의 종류를 나타내는 상수값입니다. sa_data 필드에 바이트(Byte) 형태로 주소값이 저장됩니다.

sa_data에 들어갈 값은 sa_family 포맷에 따라 다양한 형태로 저장될 수 있기 때문에, 좀 더 편리하게 작성한 다음 캐스팅(Casting)을 통해 데이터를 변환하는 것을 추천합니다.

IPv4 패킷용 주소를 만들기 위해서는 sockaddr_in 자료형을 사용할 수 있습니다.


sockaddr_in 구조체

MacOS 용 sockaddr_in

struct sockaddr_in {
  __uint8_t sin_len;
  sa_family_t sin_family;
  in_port_t sin_port;
  struct in_addr sin_addr;
  char sin_zero[8];
};

Cygwin용 sockaddr_in

struct sockaddr_in {
  sa_family_t sin_family;    /* Address family		*/
  in_port_t sin_port;    /* Port number			*/
  struct in_addr sin_addr;    /* Internet address		*/

  /* Pad to size of `struct sockaddr'. */
  unsigned char __pad[__SOCK_SIZE__ - sizeof(short int)
      - sizeof(unsigned short int) - sizeof(struct in_addr)];
};

sin_family는 메모리 레이아웃상 sockaddrsa_family와 같은 기능을 합니다.

sin_addr는 4바이트의 IPv4 주소입니다. 소켓 라이브러리마다 조금씩 다르며, 어떤 플랫폼에서는 단순히 4바이트 정수이기도 합니다. 이 때문에 여러 플랫폼에서 여러 구조체를 유니온으로 감싸둔 구조체로 값을 지정할 수 있게 해 놓았습니다.

sin_len은 플랫폼에 따라 존재하기도 하는 변수인데 구조체의 길이를 지정하는 변수입니다. 다음과 같은 형태로 사용하면 됩니다.

addr.sin_len = sizeof(addr);

바이트 주소 체계 변환

IP 주소를 바이트 형태로 묶어서 사용할 때 서버/클라이언트간 바이트 순서 체계가 다를 수 있습니다. 이런 문제를 방지하게 위해서 바이트 값들은 네트워크 순서 쳬계로 변경해서 사용해야 합니다.

uint16_t htons(uint16_t hostshort);
uint32_t htonl(uint32_t hostlong);

htons()는 부호 없는 16비트 정수를 받아서 네트워크 바이트 순서로 변환합니다. htonl()은 32비트 정수에 대해서 변환을 수행합니다.

반대의 경우는

uint16_t ntohs(uint16_t networkshort);
uint32_t ntohl(uint32_t networklong);


예제 코드

다음과 같은 코드 형태로 사용할 수 있습니다.

Server Address

  struct sockaddr_in server_address;
  memset(server_address.sin_zero, 0, sizeof(server_address.sin_zero));
  server_address.sin_family = AF_INET;
  server_address.sin_addr.s_addr = htonl(INADDR_ANY);
  server_address.sin_port = htons(80);
  server_address.sin_len = sizeof(server_address);

여기서 INADDR_ANY는 서버로 들어오는 모든 데이터를 송수신하기위한 상수값입니다.


Client Address

  struct sockaddr_in client_address;
  memset(client_address.sin_zero, 0, sizeof(client_address.sin_zero));
  client_address.sin_family = AF_INET;
  client_address.sin_port = htons(80);
  client_address.sin_addr.s_addr = inet_addr("192.168.0.10");
  client_address.sin_len = sizeof(client_address);

inet_addr() 함수의 경우, htonl()을 사용할 필요 없이 문자열의 값을 long 값으로 변환해주는 함수입니다.

운영체제별 버클리 소켓 차이점

|

버클리 소켓이 마치 표준 네트워크 API 처럼 널리 사용되고는 있지만 운영체제(OS, Operating System)별로 완전히 같지는 않습니다. OS별로 조금씩 차이가 있기 때문에 여러 플랫폼을 대상으로 개발을 할 때는 그 차이 점을 미리 알고 있는게 좋을 것 같습니다.


소켓 자료형의 차이

버클리 소켓의 데이터 자료형은 SOCKET입니다. 하지만 이 자료형을 정상적으로 갖는 OS는 Windows 10 등의 윈도우 계열에서만 가집니다. Linux, MacOS 등의 POSIX 기반의 운영체제에서는 SOCKET은 단순히 int 값을 의미하고 있습니다.

SOCKET 타입을 구조체로 하거나 int로 하는 것은 사용법에 큰 차이는 없지만 아주 약간씩의 장단점이 존재합니다.

  • int 값으로 사용하는 경우는 다양한 플랫폼 포팅이 좀 더 쉬움
  • 대신 함수의 인자형이 int이기 때문에 개발자가 실수 또는 고의로 아무 값이나 집어넣어도 컴파일러(Compiler)가 전혀 알아차리지 못함


라이브러리 헤더 파일의 차이

윈도우용 소켓은 WinSock2.h 파일을 #include 해서 사용해야 합니다. (과거 Winsock이라는 오래된 라이브러리도 있는데, 최적화나 기능면에서 부족한 점이 많아서 추천하지 않습니다. WinsockWindows.h 헤더 파일에 자동으로 포함되어 있습니다.)

Windows에서 헤더 파일간 충돌 문제

WinSock2.hWindows.h 헤더 파일을 동시에 #include 하는 경우 함수의 이름이 서로 같아 충돌하는 현상이 있습니다. 이를 해결하기 위해서는

  • Windows.h 보다 WinSock2.h 파일을 먼저 #include 하거나,
  • WIN32_LEAN_AND_MEAN 매크로를 #define 해야 합니다.


POSIX 계열에서는 sys/socket.h 헤더 파일만 #include 하면 됩니다. 그 외 다음 헤더 파일들을 추가로 포함시켜야 합니다.

헤더 파일 설명
netinet/in.h IPv4 전용 기능을 사용할 경우
arpa/inet.h 주소 변환 기능을 사용할 경우
netdb.h Name Resolution을 사용할 경우


소켓 라이브러리 초기화/활성화 방법의 차이

POSIX에서는 소켓 라이브러리가 항상 활성화 상태로 되어 있어서, 소켓을 사용할 때 별도로 해줘야 하는 작업은 없습니다. Windows 계열에서는 초기화와 해제를 해주어야 합니다. 초기화는 WSAStartup() 함수를 호출합니다.

Windows에서의 초기화 및 해제 방법

int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);

mVersionRequested는 하위 바이트는 주 버전 번호, 상위 바이트는 부 버전 번호를 의미합니다. 보통 MAKEWORD(2, 2)를 인자로 입력하면 됩니다

lpWSAData는 WSAStrtup()이 활성화하면서 라이브러리에 대한 정보로 값을 채워주기 때문에 빈 값으로 입력해도 상관없습니다.

WSAStartup() 함수는 성공하면 0을 리턴하며 실패시 에러 코드를 리턴합니다.

라이브러리의 해제는 WSACleanup() 함수를 호출하면 됩니다. WSACleanup() 함수는 레퍼런스 카운트를 사용하므로, WSAStartup() 함수가 호출된 횟수만큼 WSACleanup() 함수도 호출을 해주어야 합니다.

int WSACleanup();

해제시, 현재 진행 중인 모든 소켓의 동작이 강제 종료되며, 리소스는 모두 소멸이 됩니다. 따라서 라이브러리 해제 전에 모든 소켓의 마무리 작업을 먼저 진행하는 편이 좋습니다.


에러 코드 확인

Windows 계열에서는 에러 값을 WSAGetLastError() 함수를 이용해서 조회를 합니다.

int WSAGetLastError();

반면 POSIX 계열에서는 전역 error 변수를 사용하기 때문에 error.h 헤더 파일을 #include 해서 error 변수 값을 확인하면 됩니다.

TextView, EditView의 SingleLine 속성 Deprecated 해결

|

SingleLine Deprecated

TextView나 EditView에서 한 줄만 표시되도록 하는 속성은 singleLine이었습니다. 하지만 지금은 Deprecated 된 속성입니다.

따라서 다른 방법을 찾아봐야 합니다.

다음과 같은 속성을 추가하면 기존 singleLine과 동일한 효과를 냅니다.

android:inputType="text"
android:maxLines="1"

EditText 예제

<EditText
  android:id="@+id/input"
  android:layout_width="0px"
  android:layout_height="wrap_content"
  android:layout_weight="4"
  android:inputType="text"
  android:maxLines="1" />

ListView의 구분선 삭제하기

|

ListView의 구분선 삭제

ListView에서 각 칸을 구분하는 구분선 삭제는 XML에서 ListView의 속성에 다음 속성을 추가하면 됩니다.

android:divider="@null"
android:dividerHeight="0dp"


ListView 예제

  <ListView
    android:id="@+id/listview"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_above="@id/layout_input"
    android:divider="@null"
    android:dividerHeight="0dp" />

버클리 소켓(Berkely Socket)에 대하여

|

버클리 소켓이란

버클리 소켓은 원래 BSD 4.2 운영체제의 일부로 배포되었다가, 그 후 여러 OS들이나 플랫폼, 프로그래밍 언어 등에 포팅되면서 네트워크 프로그래밍의 표준처럼 여겨지게 되었습니다.


소켓 생성

소켓 생성은 다음 명령어를 이용해서 할 수 있습니다.

SOCKET socket(int af, int type, int protocol);


Address Family

af 파라메터는 주소 패밀리(Address Family)를 의미하며, 다음과 같은 값을 가질 수 있습니다.

상수값 의미
AF_UNSPEC 지정하지 않음
AF_INET 인터넷 프로토콜 (IPv4)
AF_INET6 인터넷 프로토콜 (IPv6)
AF_IPX IPX(Internetwork Packet Exchange) 프로토콜


Type

type 파라메터는 소켓을 통해서 주고 받는 패킷의 종류를 의미합니다.

상수값 의미
SOCK_STREAM 순서가 보장되는 데이터 스트림
SOCK_DGRAM 데이터그램(Datagram)을 패킷을 전송
SOCK_RAW Raw level의 패킷을 전송
SOCK_SEQPACKET SOCK_STREAM과 비슷하나 패킷 수신 시 항상 전체를 읽어들여야 함

SOCK_STREAM 형태의 타입으로 지정을 하면 소켓은 상태유지형(stateful) 연결 형태가 됩니다. 신뢰성이 있고 순서가 보장되는 스트림(Stream) 형태로 데이터를 처리할 수 있습니다. TCP 프로토콜에 어울리는 소켓 형식입니다.

SOCK_DGRAM 타입은 UDP 프로토콜에 어울리는 소켓 형식으로 연결 상태를 유지할 필요가 없기 때문에 최소한의 리소스만 할당하여, 개별 데이터그램 단위로만 데이터를 주고 받을 수 있게 됩니다. 신뢰성이나 패킷의 순서를 보장할 필요가 없습니다.


Protocol

protocol 파라메터는 소켓이 실제로 사용할 프로토콜의 종류를 명시합니다.

상수값 필요 소켓 타입 의미
IPPROTO_TCP SOCK_STREAM TCP 세그먼트 패킷
IPPROTO_UDP SOCK_DGRAM UDP 데이터그램 패킷
IPPROTO_IP 또는 0 상관없음 주어진 소켓 종류의 디폴트 프로토콜을 사용

protocol을 ‘0’으로 하면 운영체제가 알아서 적절한 프로토콜을 선택해주기 때문에 다음과 같이 사용해도 됩니다.

SOCKET tcpSocket = socket(AF_INET, SOCK_STREAM, 0);

SOCKET udpSocket = socket(AF_INET, SOCK_DGRAM, 0);


소켓 종료

소켓을 종료할 때는 다음 코드로 종료합니다.

int closesocket(SOCKET socket);

만약 소켓을 닫기 전에 전송과 수신을 종료하려면

int shutdown(SOCKET socket, int how);

함수를 호출하면 됩니다. how 파라메터는 다음과 같습니다.

상수값 의미
SD_SEND 전송을 중단
SD_RECEIVE 수신을 중단
SD_BOTH 송수신을 모두 중단