> For the complete documentation index, see [llms.txt](https://docs.aic-eec.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.aic-eec.com/computer-operation-systems/linux-os-dev.-engineer/ipc/socket-programming.md).

# Socket Programming

BSD Sockets

<figure><img src="https://1856353139-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MClo3nC-1US0rbK8Qau%2Fuploads%2FR7YSfPciCTVU4lolTv4r%2FIPC%20overview.png?alt=media&amp;token=c06a50ff-e3c2-4d83-9f7f-3b4f4a469e3d" alt=""><figcaption><p>ประเภทการกลไกสื่อสาร IPC</p></figcaption></figure>

เริ่มต้นตั้งแต่ระบบปฏิบัติการ Berkeley UNIX (BSD) รุ่น 4.2 โดย BSD ได้ถูกปล่อยออกสู่สาธารณะ ก็มีความสามารถในการรองรับรูปแบบการสื่อสารระหว่างโปรเซสและระบบเครือข่ายด้วยวิธีการแบบ socket communication ซึ่งแนวคิดของ <mark style="color:orange;">**Socket**</mark> คือเพื่อให้การสื่อสารระหว่างโปรเซสดำเนินการผ่านระบบไฟล์ I/O โดยมีการใช้ file descriptor ร่วมกัน โดยทั่วไปแล้วตัวโปรเซสลูกที่เกิดขึ้นจะได้รับสืบทอดตัว file descriptors มาจากโปรเซสแม่ เฉกเช่นเดียวกับกลไกการทำงานของ pipe แต่การสื่อสารด้วยวิธีการแบบ pipe เองจะเป็นแบบทิศทางเดียว (single direction) และก็สื่อสารกันได้เพียงภายในเครื่องคอมพิวเตอร์เครื่องเดียวกันเท่านั้น

ดังนั้นระบบปฏิบัติการ Berkeley UNIX ก็ได้นำเสนอแนวคิดการสื่อสารระหว่างโปรเซสที่สามารถสื่อสารกันภายในเครื่องคอมพิวเตอร์เดียวกัน หรือต่างเครื่องคอมพิวเตอร์ได้แต่จะต้องอยู่ภายใต้ระบบเครือข่าย TCP/IP (TCP/IP networking) รวมทั้งยังรองรับการสื่อสารแบบสองทิศทาง (bidirectional) ได้เช่นกัน ดังนั้นเพื่อให้การสื่อสารสามารถคุยได้สองทิศทางระหว่างโปรเซสแม่และโปรเซสลูกภายในเครื่องคอมพิวเตอร์เดียวกัน สามารถทำได้โดยการเรียกใช้ฟังก์ชัน socketpair() ซึ่งเป็นฟังก์ชันที่ถูกให้ทำงานได้เพียงโดเมนเดียว ซึ่งถูกเรียกว่า UNIX domain โดยการการใช้ `AF_UNIX` (Address Format UNIX) และ `SOCK_STREAM` ที่อยู่ในไลบรารี `sys/socket.h` และไลบรารี `sys/types.h` ดังตัวอย่างโปรแกรมข้างล่างนี้

```c
// simple_socket.c
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

#define DATA1 "This data is sent from a child process . . ."
#define DATA2 "This data is sent from a parent process . . ."

/*
 * This  program creates a  pair of connected sockets,
 * then forks and communicates over them.  This is very 
 * similar to communication with pipes, however, socketpairs
 * are  two-way  communications  objects.  Therefore I can
 * send messages in both directions.
 */

main() {
	int sockets[2], child;
	char buf[1024];

	if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) < 0) {
		perror("opening stream socket pair");
		exit(1);
	}

	if ((child = fork()) == -1)
		perror("fork");
	else if (child) { /* This is the parent. */
		close(sockets[0]);
		if (read(sockets[1], buf, sizeof(buf)) < 0)
			perror("reading stream message");
		printf("-->%s\n", buf);
		if (write(sockets[1], DATA2, sizeof(DATA2)) < 0)
			perror("writing stream message");
		close(sockets[1]);
	} else { /* This is the child. */
		close(sockets[1]);
		if (write(sockets[0], DATA1, sizeof(DATA1)) < 0)
			perror("writing stream message");
		if (read(sockets[0], buf, sizeof(buf)) < 0)
			perror("reading stream message");
		printf("-->%s\n", buf);
		close(sockets[0]);
	}
}
```

```shell-session
$ gcc -o simple_socket simple_socket.c 
$ ./simple_socket
-->This data is sent from a child process . . .
-->This data is sent from a parent process . . .
```

จากตัวอย่างโปรแกรมข้างต้นจะเห็นว่าการใช้ `socketpairs` (หรือแม้กระทั่ง pipes) นั้นถูกจำกัดในเรื่องของการสื่อสารข้อมูลระหว่างโปรเซสได้เฉพาะกลุ่มโปรเซสที่อยู่ในตระกูลเดียวกัน (โปรเซสแม่ที่มีการสร้างโปรเซสลูกเพิ่มขึ้นมา) โดยเมื่อไหร่ก็ตามที่มีการเกิดของแต่ละโปรเซสที่ไม่ได้อยู่ในตระกูลเดียวกันหรือโปรเซสที่ทำงานอยู่คนละเครื่องคอมพิวเตอร์แล้ว แต่ละโปรเซสจะสื่อสารกันทันทีไม่ได้ แต่จะต้องสร้าง socket ของตัวเองขึ้นมาเพื่อเอาไว้ส่งและรับข้อมูล โดยจะต้องมีการระบุชื่อ (name) ให้กับ socket เพื่อใช้ในการอ้างอิงถึงกัน เมื่อเริ่มใช้งานชื่อเหล่านั้นจะต้องถูกแปลงให้เป็นหมายเลขที่อยู่ (address) ซึ่งเลขที่อยู่ของแต่ละ socket จะถูกระบุให้อยู่ภายในพื้นที่หรือโดเมนเดียวกัน&#x20;

{% hint style="info" %}
ชื่อโดเมนสำหรับ socket มีอยู่หลายแบบ แต่ที่เป็นที่รู้จักและนิยมก็มีอยู่ 2 ประเภทคือ <mark style="color:purple;">**UNIX domain**</mark> (AF\_UNIX) และ <mark style="color:orange;">**Internet domain**</mark> (AF\_INET)
{% endhint %}

โดยเฉพาะในกรณีที่โปรเซสทำงานแยกกันอยู่คนละเครื่องคอมพิวเตอร์ที่ซึ่งถูกเชื่อมต่ออยู่บนระบบเครือข่ายอินเทอร์เน็ท (TCP/IP) ต้องการจะสื่อสารข้อมูลระหว่างกัน แต่ละโปรเซสจะต้องมีการสร้างชื่อสำหรับ socket ขึ้นมาภายใน Internet domain เดียวกันซึ่งถูกพัฒนาอยู่ในระบบปฏิบัติการ UNIX ที่มีการใช้โปรโตคอลมาตราฐานสำหรับระบบเครือข่ายที่ถูกกำหนดโดยหน่วยงาน DAPRA ได้แก่ IP, TCP และ UDP โดยที่หมายเลขที่อยู่ (address) ใน Internet domain นั้นจะประกอบไปด้วยที่อยู่เครือข่ายของเครื่อง (machine network address) และ หมายเลขพอร์ต (port address)&#x20;

## Socket นั้นมีอยู่ด้วยกัน 3 ประเภทโดยยึดตามมาตราฐานของเทคโนโลยี TCP/IP ได้แก่

<table><thead><tr><th width="189">Socket</th><th>คำอธิบาย</th></tr></thead><tbody><tr><td>Datagram Socket</td><td>เรียกอีกชื่อหนึ่งว่า Connection less Socket ซึ่งใช้โปรโตคอล UDP (User Datagram Protocol) เป็นตัวกำหนดวิธีการสื่อสาร โดยข้อมูลหรือแพ็กเก็ต (packet) แต่ละตัวจะถูกส่งบน datagram socket ที่แยกเส้นทางกันออกไป ดังนั้นแพ็กเก็ตที่ส่งจากเครื่องต้นทางก็จะถูกลำเลียงกระจายออกไปในแต่ละเส้นทาง จนถึงเครื่องรับปลายทาง โดยแต่ละแพ็กเก็ตก็อาจจะมาถึงเครื่องปลายทางแบบไม่ได้เรียงตามลำดับตามที่ถูกส่งออกจากเครื่องต้นทางก่อนหน้านั้น</td></tr><tr><td>Stream Socket</td><td>เรียกอีกชื่อหนึ่งว่า Connection-oriented Socket ซึ่งใช้โปรโตคอล TCP (Transport Control Protocol) เป็นตัวกำหนดวิธีการสื่อสาร ซึ่งจะมีการสถาปนาการเชื่อมต่อและการันตีการรับส่งแพ็กเก็ต ดังนั้นข้อมูลหรือแพ็กเก็ตแต่ละตัวจะถูกส่งบน stream socket และจะถูกลำเลียงส่งผ่านช่องทางที่ถูกสร้างขึ้นมานี้ไปยังเครื่องปลายทางจนครบถ้วนสมบูรณ์</td></tr><tr><td>Raw Socket</td><td>ส่วนใหญ่จะพบในอุปกรณ์เครือข่ายเช่น สวิทซ์ (Switch) และ เราเตอร์ (Router) ซึ่งทำงานอยู่ในระดับ Internet Layer ที่มีการรับส่งข้อมูลโดยไม่ได้มีการใช้โปรโตคอลเหมือน datagram และ stream socket ในการกำหนดมาตราฐานการสื่อสาร</td></tr></tbody></table>

เมื่อโปรเซสทั้งสองที่อยู่ต่างเครื่องคอมพิวเตอร์ต้องการสื่อสารระหว่างกันจะต้องมีสถาปนาการเชื่อมต่อด้วยขั้นตอนตามเทคโนโลยี TCP/IP ซึ่งอยู่ในระดับชั้น transport (transport layer) และจะต้องมีการระบุหมายเลขพอร์ต (port address) ไปยังโปรโตคอลแอพพิเคชั่น (application protocol) ที่โปรเซสแต่ละฝั่งใช้อยู่ด้วย ตัวอย่างเช่นโปรแกรมรับส่งไฟล์ที่ใช้โปรโตคอล FTP ในการกำหนดควบคุมวิธีการส่งไฟล์ระหว่างกัน เรียกการพัฒนาโปรแกรมทางด้านนี้ว่า socket programming&#x20;

<figure><img src="https://1856353139-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MClo3nC-1US0rbK8Qau%2Fuploads%2FubPYQXgSUEywy8JvUEr1%2FTCPIP.png?alt=media&amp;token=ed635dbf-8a4c-41cd-b70a-626bf6d699c7" alt=""><figcaption><p><em>แสดงการเชื่อมต่อระดับโปรแกรมประยุกต์และหมายเลขพอร์ตของ TCP/IP</em></p></figcaption></figure>

การพัฒนาโปรแกรมทางด้าน socket จะต้องทำความเข้าใจวิธีการตั้งค่าหมายเลข IP address แต่ละตัว ไม่ว่าจะเป็นหมายเลขเครือข่าย (network ID) หมายเลขเครื่อง (host ID) และหมายเลข netmask address&#x20;

ความสัมพันธ์ระหว่างโปรแกรม, socket, protocol และหมายเลขพอร์ตภายในเครื่องคอมพิวเตอร์เพื่อใช้ในการสื่อสารข้อมูลระหว่างโปรเซสที่อยู่ต่างเครื่องกัน ประเด็นที่น่าสนใจที่นักพัฒนาควรรู้ได้แก่

* โปรแกรมหนึ่งโปรแกรมสามารถเปิดซ็อกเก็ตได้หลายซ็อกเก็ตเพื่อรับการเชื่อมต่อจากเครื่องภายนอกได้พร้อมกันในเวลาเดียวกัน
* โปรแกรมหลายโปรแกรมสามารถใช้ซ็อกเก็ตตัวเดียวกันในเวลาเดียวกันแต่ไม่ค่อยพบเห็นการใช้ในลักษณะนี้
* มากกว่าหนึ่งซ็อกเก็ตที่สามารถถูกเกี่ยวข้องและใช้งานพอร์ตอันเดียวกัน
* โปรแกรมประยุกต์แต่ละตัวจะมีการใช้ทั้ง TCP และ UDP เพื่อใช้ในการจัดการและรับส่งข้อมูล

ตามวัตถุประสงค์ที่แตกต่างกันไป เช่น ต้องการเน้นความเร็วในการส่งแม้ข้อมูลจะหายได้บ้างก็จะใช้ช่องทาง UDP หรือถ้าเน้นความถูกต้องของข้อมูลโดยที่ข้อมูลจะต้องไม่สูญหายก็จะใช้ช่องทาง TCP ในการรับส่งแทน

* ไฟล์ /etc/services ภายในระบบปฏิบัติการลีนุกซ์จะบอกรายละเอียดของโปรแกรมให้บริการและโปรโตคอลที่ใช้หมายเลขพอร์ตในการสื่อสารผ่านซ็ิอกเก็ตในระดับของ Transport layer

<figure><img src="https://1856353139-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MClo3nC-1US0rbK8Qau%2Fuploads%2FPFTysGIT6enLHorguYxA%2FSocket.png?alt=media&amp;token=58e69f28-51ae-4597-b813-3ec5df3cbb2b" alt=""><figcaption><p><em>แสดงขบวนการเชื่อมต่อระหว่างเครื่องภายในระบบเครือข่ายผ่าน Socket</em></p></figcaption></figure>

{% hint style="info" %}
**ฟังก์ชันและตัวแปรที่เกี่ยวข้อง**

* ไลบรารีที่เกี่ยวข้องคือ <mark style="color:orange;">**`sys/types.h`**</mark>**,**<mark style="color:orange;">**`sys/socket.h`**</mark>**,**<mark style="color:orange;">**`unistd.h`**</mark>**,**<mark style="color:orange;">**`netinet/in.h`**</mark>และ<mark style="color:orange;">**`sys/un.h`**</mark>
* ข้อมูลตัวแปรที่สำคัญ&#x20;
  * ตัวแปร `struct sockaddr` สำหรับระบุข้อมูลพื้นฐานของโดเมนทั้ง `AF_UNIX` และ `AF_INET`
  * ตัวแปร `struct sockaddr_in` สำหรับระบุคุณลักษณะของ socket ใน Internet domain โดยมีรายละเอียดดังนี้&#x20;

```c
struct sockaddr {
	unsigned short sa_family;    // address family, AF_xxx
	char sa_data[14];  // 14 bytes of protocol address
};

struct sockaddr_in {
	u_char sin_len;
	u_char sin_family; /* = AF_INET */
	u_short sin_port; /* use htons() to set this */
	struct in_addr sin_addr;
	char sin_zero[8]; /* 8 zeros required here */
};

struct in_addr {
	in_addr_t s_addr;
/* either use a macro from netinet/in.h (eg INADDR_ANY), or inet_addr */
};
```

* ฟังก์ชัน <mark style="color:green;">**`int socket(int domain, int communication_type, int protocol);`**</mark>&#x20;
  * เพื่อใช้สร้าง socket โดยระบุโดเมนที่ต้องการ (domain) เช่น `AF_INET` และระบุประเภทของการสื่อสาร (communication\_type) เช่น `SOCK_STREAM` โดยโปรโตคอลก็ขึ้นอยู่กับชนิดของโดเมน
    {% endhint %}

## ประเภทของการเชื่อมต่อสื่อสารระหว่างเครื่องคอมพิวเตอร์ผ่าน socket นั้นแบ่งออกได้เป็น 2 ประเภทได้แก่&#x20;

1. การสื่อสารแบบมีการสถาปนาการเชื่อมต่อ (<mark style="color:blue;">**connection-oriented**</mark> หรือ <mark style="color:blue;">**connectionful**</mark>) หมายถึงก่อนที่จะมีการสื่อสารข้อมูลระหว่างกันทั้งสองเครื่องจะต้องมีการสถาปนาการเชื่อมต่อให้เสร็จเรียบร้อยก่อน (point-to-point) เพื่อให้การสื่อสารข้อมูลผ่านท่อนี้มีการส่งข้อมูลที่ครบถ้วนและมีถูกต้องของข้อมูลมากที่สุด โดยมีขั้นตอนคร่าวๆดังต่อไปนี้
   * สร้าง socket และระบุโดเมน  -- `socket()`
   * ทำการผูกรายละเอียดของ socket และหมายเลขพอร์ต -- `bind()`
   * ทำการเปิดพอร์ตเพื่อรอการขอการเชื่อมต่อจากภายนอก -- `listen()`
   * เมื่อมีการร้องขอการเชื่อมต่อก็จะตอบรับและเตรียมสถาปนาการเชื่อมต่อระหว่างกัน -- `accept()`
   * เริ่มการสื่อสารข้อมูลระหว่างกัน -- `write()` and `read()`
   * สิ้นสุดการเชื่อมต่อ -- `close()`
2. การสื่อสารแบบไม่ต้องมีการสถาปนาการเชื่อมต่อ (<mark style="color:orange;">**connectionless**</mark>) หมายถึงเครื่องคอมพิวเตอร์สามารถสื่อสารข้อมูลออกไปได้ทันทีโดยที่ไม่ต้องมีการสถาปนาการเชื่อมต่อแต่อย่างใด นักพัฒนาโปรแกรมก็จะต้องหาวิธีการเทคนิคต่างๆเพื่อทำให้การสื่อสารข้อมูลถูกต้องด้วยตัวเอง โดยมีขั้นตอนคร่าวๆดังต่อไปนี้
   * สร้าง socket และระบุโดเมน  -- `socket()`
   * ทำการผูกรายละเอียดของ socket และหมายเลขพอร์ต -- `bind()`
   * เริ่มการสื่อสารข้อมูลระหว่างกันได้ทันที -- `sendto()` and `recvfrom()`
   * สิ้นสุดการเชื่อมต่อ -- `close()`

โดยค่าปริยายแล้วการทำงานของ sockets จะเป็นในลักษณะแบบถูกกำหนดให้หยุดรอ (blocking) ดังนั้นฟังก์ชันต่างๆจะต้องถูกหยุดอยู่ชั่วขณะจนกว่างานที่ร้องขอบน socket จะสิ้นสุดลง แต่อย่างไรก็ตามก็สามารถตั้งค่าให้ socket ทำงานแบบ non-blocking ได้โดยการใช้ฟังก์ชัน fcntl

{% hint style="success" %}

### **ตัวอย่างที่ 1**

{% endhint %}

ตัวอย่างโปรแกรมสื่อสารผ่าน socket ในลักษณะการเชื่อมต่อแบบ connectionless (SOCK\_DGRAM) โดยเครื่องลูก (datagram client) จะเชื่อมต่อไปยังเครื่องแม่ (datagram server) แล้วทำการส่งข้อความไปยังเครื่องแม่ เมื่อเครื่องแม่ได้รับข้อความก็จะแสดงออกทางหน้าจอของดังตัวอย่างโปรแกรมข้างล่างนี้

โปรแกรมฝั่งเครื่องแม่ (Server):

```c
/*
 ** dgram_server.c -- a datagram sockets "server"
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define MYPORT 4950 // the port users will be connecting to
#define MAXBUFLEN 100

int main(void) {
	int sockfd;
	struct sockaddr_in my_addr; // my address information
	struct sockaddr_in their_addr; // connector's address information
	socklen_t addr_len;
	int numbytes;
	char buf[MAXBUFLEN];

	if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
		perror("socket");
		exit(1);
	}

	my_addr.sin_family = AF_INET; // host byte order
	my_addr.sin_port = htons(MYPORT); // short, network byte order
	my_addr.sin_addr.s_addr = INADDR_ANY; // automatically fill with my IP
	memset(&(my_addr.sin_zero), '\0', 8); // zero the rest of the struct

	if (bind(sockfd, (struct sockaddr*) &my_addr, sizeof(struct sockaddr))
			== -1) {
		perror("bind");
		exit(1);
	}
	while (strcmp(buf, "close")) {
		addr_len = sizeof(struct sockaddr);
		if ((numbytes = recvfrom(sockfd, buf, MAXBUFLEN - 1, 0,
				(struct sockaddr*) &their_addr, &addr_len)) == -1) {
			perror("recvfrom");
			exit(1);
		}

		printf("got packet from %s\n", inet_ntoa(their_addr.sin_addr));
		printf("packet is %d bytes long\n", numbytes);
		buf[numbytes] = '\0';
		printf("packet from client contains \"%s\"\n", buf);
	}

	close(sockfd);

	return 0;
}
```

โปรแกรมฝั่งเครื่องลูก (Client):

```c
/*
 ** dgram_client.c -- a datagram "client" 
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>


#define SERVERPORT 4950 // the port users will be connecting to
#define MAXBUFLEN 100


int main(int argc, char *argv[]) {
int sockfd;
struct sockaddr_in their_addr; // connector's address information
struct hostent *he;
int numbytes, i;
char buf[MAXBUFLEN];


if (argc != 2) {
fprintf(stderr, "usage: ./dgram_client <hostname>\n");
exit(1);
}


if ((he = gethostbyname(argv[1])) == NULL) {  // get the host info
perror("gethostbyname");
exit(1);
}


if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
perror("socket");
exit(1);
}


their_addr.sin_family = AF_INET; // host byte order
their_addr.sin_port = htons(SERVERPORT); // short, network byte order
their_addr.sin_addr = *((struct in_addr *) he->h_addr);
memset(&(their_addr.sin_zero), '\0', 8);  // zero the rest of the struct
while (strcmp(buf, "close")) {
fgets(buf, MAXBUFLEN, stdin);
for (i = 0; buf[i] != '\n'; i++)
;
buf[i] = '\0';
if ((numbytes = sendto(sockfd, buf, strlen(buf), 0,
(struct sockaddr *) &their_addr, sizeof(struct sockaddr)))
== -1) {
perror("sendto");
exit(1);
}


printf("sent %d bytes to %s\n", numbytes,
inet_ntoa(their_addr.sin_addr));
}
close(sockfd);


return 0;
}
```

&#x20; ทำการคอมไพล์และรันโปรแกรมโดยแยกหน้าต่าง terminal ดังนี้

{% tabs %}
{% tab title="หน้าต่าง terminal ที่ 1" %}

```shell-session
$ gcc -o dgram_server dgram_server.c
$ ./dgram_server
got packet from 127.0.0.1
packet is 20 bytes long
packet from client contains "Hello from Client..."
```

{% endtab %}

{% tab title="หน้าต่าง terminal ที่ 2" %}

```shell-session
$ gcc -o dgram_client dgram_client.c
$ ./dgram_client 127.0.0.1
Hello from Client...    
sent 20 bytes to 127.0.0.1
```

{% endtab %}
{% endtabs %}

{% hint style="success" %}

### ตัวอย่างที่ 2

{% endhint %}

ตัวอย่างโปรแกรมเชื่อมต่อผ่านซ็อกเก็ตในลักษณะ connection-oriented (`SOCK_STREAM`) เมื่อทำการสถาปนาการเชื่อมต่อสำเร็จโดยเครื่องลูก (client) จะเชื่อมต่อไปยังเครื่องแม่ (server) แล้วทำการส่งข้อความไปยังเครื่องแม่ เมื่อเครื่องแม่ได้รับข้อความก็จะแสดงออกทางหน้าจอของดังตัวอย่างโปรแกรมข้างล่างนี้

โปรแกรมฝั่งเครื่องแม่ (Server):

```c
/*
 ** strm_server.c -- a stream sockets "server"
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>

#define TRUE 1

main() {
	int sock, length;
	struct sockaddr_in server;
	int msgsock;
	char buf[1024];
	int rval;
	int i;

	/* Create socket */
	sock = socket(AF_INET, SOCK_STREAM, 0);
	if (sock < 0) {
		perror("opening stream socket");
		exit(1);
	}
	/* Name socket using wildcards */
	server.sin_family = AF_INET;
	server.sin_addr.s_addr = INADDR_ANY;
	server.sin_port = 0;
	if (bind(sock, (struct sockaddr*) &server, sizeof(server))) {
		perror("binding stream socket");
		exit(1);
	}
	/* Find out assigned port number and print it out */
	length = sizeof(server);
	if (getsockname(sock, (struct sockaddr*) &server, &length)) {
		perror("getting socket name");
		exit(1);
	}
	printf("Socket has port #%d\n", ntohs(server.sin_port));

	/* Start accepting connections */
	listen(sock, 5);
	do {
		msgsock = accept(sock, 0, 0);
		if (msgsock == -1) {
			perror("accept");
			return EXIT_FAILURE;
		} else
			do {
				memset(buf, 0, sizeof(buf));
				if ((rval = read(msgsock, buf, 1024)) < 0)
					perror("reading stream message");
				else if (rval == 0)
					printf("Ending connection\n");
				else
					printf("-->%s\n", buf);
			} while (rval > 0);
		close(msgsock);
	} while (TRUE);
}
```

โปรแกรมฝั่งเครื่องลูก (Client):

```c
/*
 ** strm_client.c -- a stream sockets "client"
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

#define DATA "Hello message from Client..."

main(int argc, char *argv[]) {
	int sock;
	struct sockaddr_in server;
	struct hostent *hp,* gethostbyname();

	/* Create socket */
	sock = socket(AF_INET, SOCK_STREAM, 0);
	if (sock < 0) {
		perror("opening stream socket");
		exit(1);
	}
	/* Connect socket using name specified by command line.  */
	server.sin_family = AF_INET;
	hp = gethostbyname(argv[1]);
	if (hp == 0) {
		fprintf(stderr, "%s: unknown host\n", argv[1]);
		exit(2);
	}
	memcpy(&server.sin_addr, hp->h_addr, hp->h_length);
	server.sin_port = htons(atoi(argv[2]));
	if (connect(sock, (struct sockaddr*) &server, sizeof(server)) < 0) {
		perror("connecting stream socket");
		exit(1);
	}
	if (write(sock, DATA, sizeof(DATA)) < 0)
		perror("writing on stream socket");
	close(sock);
}
```

&#x20; ทำการคอมไพล์และรันโปรแกรมโดยแยกหน้าต่าง terminal ดังนี้

{% tabs %}
{% tab title="หน้าต่าง terminal ที่ 1" %}

```shell-session
$ gcc -o strm_server strm_server.c
$ ./strm_server
Socket has port #37219
--> Hello message from Client...
Ending connection
```

{% endtab %}

{% tab title="หน้าต่าง terminal ที่ 2" %}

```shell-session
$ gcc -o strm_client strm_client.c
$ ./strm_client 127.0.0.1 37219
```

{% endtab %}
{% endtabs %}

&#x20; จากการทำงานของโปรแกรมข้างต้น เมื่อโปรแกรม `strm_server` เริ่มทำงานบนเครื่องแม่แล้ว จะแจ้งหมายเลขพอร์ตที่เปิดรอให้เครื่องอื่นเข้ามาเชื่อมต่อ หลังจากนั้นเมื่อรันโปรแกรม `strm_client` ที่อยู่ในฝั่งของเครื่องลูกโดยระบุอาร์กิวเมนต์ตัวแรกเป็นหมายเลขที่อยู่ของเครื่องแม่ (`127.0.0.1`) และตัวที่สองเป็นหมายเลขพอร์ตที่ถูกเปิดในเครื่องแม่ (`37219`) จนกระทั่งการเชื่อมต่อระหว่างทั้งสองเครื่องถูกสถาปนาเรียบร้อยแล้ว ข้อความที่ถูกกำหนดไว้ในโปรแกรม strm\_client ก็จะถูกส่งไปยังเครื่องแม่อัตโนมัติดังผลลัพธ์ข้างบน

ฟังก์ชัน `accept()` ภายในโปรแกรม `strm_server` จะถูกให้หยุดรอฟังการร้องขอการเชื่อมต่อจากเครื่องคอมพิวเตอร์ภายนอก ซึ่งถ้ายังไม่มีการร้องขอการเชื่อมต่อหรือข้อมูลที่อ่านได้มาจาก socket ยังมาไม่ครบ ณ เวลานั้นตัวโปรแกรม `strm_server` ก็ยังคงหยุดรอโดยไม่สามารถไปทำงานอย่างอื่นได้เลย&#x20;

ดังนั้นเพื่อให้การทำงานของโปรแกรม `strm_server` กลายเป็นแบบ non-blocking กล่าวคือโปรแกรมยังสามารถทำงานอย่างอื่นได้อยู่ โดยที่ไม่ต้องหยุดรอเหมือนโปรแกรมข้างต้น ด้วยการเรียกใช้ฟังก์ชัน `select()` เพื่อทำการตรวจสอบว่าเมื่อใดที่มีการร้องขอการเชื่อมต่อ socket จากเครื่องภายนอก ก็จะค่อยไปเรียกฟังก์ชัน `accept()` ให้ทำงานทันที ด้วยวิธีการนี้เองสามารถประยุกต์ให้โปรแกรมบนเครื่องแม่ สามารถเปิดรับการเชื่อมต่อได้มากกว่าหนึ่ง socket ดังตัวอย่างโปรแกรม `new_strm_server.c` ที่ปรับปรุงใหม่ข้างล่างนี้

```c
/*
 ** new_strm_server.c -- a stream sockets "server"
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <sys/select.h>
#include <netdb.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>

#define TRUE 1

/*
 * This program uses select() to check that someone
 * is trying to connect before calling accept().
 */

main() {
	int sock, length;
	struct sockaddr_in server;
	int msgsock;
	char buf[1024];
	int rval;
	fd_set ready;
	struct timeval to;

	/* Create socket */
	sock = socket(AF_INET, SOCK_STREAM, 0);
	if (sock < 0) {
		perror("opening stream socket");
		exit(1);
	}
	/* Name socket using wildcards */
	server.sin_family = AF_INET;
	server.sin_addr.s_addr = INADDR_ANY;
	server.sin_port = 0;
	if (bind(sock, (struct sockaddr*) &server, sizeof(server))) {
		perror("binding stream socket");
		exit(1);
	}
	/* Find out assigned port number and print it out */
	length = sizeof(server);
	if (getsockname(sock, (struct sockaddr*) &server, &length)) {
		perror("getting socket name");
		exit(1);
	}
	printf("Socket has port #%d\n", ntohs(server.sin_port));

	/* Start accepting connections */
	listen(sock, 5);
	do {
		FD_ZERO(&ready);
		FD_SET(sock, &ready);
		to.tv_sec = 5;
		if (select(sock + 1, &ready, 0, 0, &to) < 0) {
			perror("select");
			return EXIT_FAILURE;
		}
		if (FD_ISSET(sock, &ready)) {
			msgsock = accept(sock, (struct sockaddr*) 0, (int*) 0);
			if (msgsock == -1) {
				perror("accept");
				return EXIT_FAILURE;
			} else
				do {
					memset(buf, 0, sizeof(buf));
					if ((rval = read(msgsock, buf, sizeof(buf))) < 0)
						perror("reading    stream message");
					else if (rval == 0)
						printf("Ending connection\n");
					else
						printf("-->%s\n", buf);
				} while (rval > 0);
			close(msgsock);
		} else
			printf("Do something else...\n");
	} while (TRUE);
}
```

ทำการคอมไพล์และรันโปรแกรมอีกครั้งโดยแยกหน้าต่าง terminal ดังนี้

{% tabs %}
{% tab title="หน้าต่าง terminal ที่ 1" %}

```shell-session
$ gcc -o new_strm_server new_strm_server.c
$ ./new_strm_server
Socket has port #45480
--> Hello message from Client...
Ending connection
Do something else
Do something else
Do something else
```

{% endtab %}

{% tab title="หน้าต่าง terminal ที่ 2" %}

```shell-session
$ ./strm_client 127.0.0.1 45480
```

{% endtab %}
{% endtabs %}

&#x20;

&#x20;&#x20;
