نمودار گردشی سوکت‌های TCP
**سوکت‌های برکلی** (به انگلیسی: Berkeley sockets) یا **سوکت‌های بی‌اس‌دی** (به انگلیسی: BSD sockets) کتاب‌خانه‌ای شامل رابط‌های برنامه‌نویسی نرم‌افزار برای کار با سوکت‌های اینترنتی و سوکت‌های دامنه یونیکس است، که از این سوکت‌ها برای ارتباطات بین پردازشی استفاده می‌شوند. سوکت‌های برکلی به عنوان رابط برنامه‌نویسی نرم‌افزار از سیستم‌عامل ۴/۲بی‌اس‌دی سرچشمه گرفتند که این سیستم‌عامل در سال ۱۹۸۳ منتشر شد. امروزه تمام سیستم‌عامل‌های مدرن یک پیاده‌سازی از سوکت‌های برکلی را به همراه دارند چون این سوکت‌ها روش استاندارد برای دسترسی به اینترنت هستند. این رابط‌ها در اصل به زبان سی نوشته شدند اما بیشتر زبان‌های برنامه‌نویسی رابط‌های مشابهی را در دسترس کاربر قرار می‌دهند و می‌توان از آن‌ها در اکثر زبان‌های برنامه‌نویسی مدرن استفاده کرد.

فایل‌های سرآیند
---------------

رابط‌های سوکت برکلی در چند فایل سرآیند تعریف شده‌اند. البته در پیاده‌سازی‌های مختلف، این فایل‌ها کمی در نام و محتوا با هم متفاوت هستند. اما به‌طور کلی این فایل‌های سرآیند به شرح زیر هستند:

`<sys/socket.h>`
توابع و ساختمان داده‌های اصلی مرتبط با سوکت‌ها در این فایل تعریف شده‌است.

`<netinet/in.h>`
دربرگیرنده AF\_INET و AF\_INET6 و پروتکل‌های متناظر با آن‌ها PF\_INET و PF\_INET6 است. این پروتکل‌ها به‌طور گسترده در اینترنت استفاده می‌شوند. این فایل هم دربرگیرنده آدرس‌های IP و هم شماره پورت‌های TCP و UDP است.

`<sys/un.h>`
این فایل حاوی خانواده آدرس‌های PF\_UNIX/PF\_LOCAL است. این دسته از آدرس‌ها برای برقرای ارتباط میان برنامه‌های موجود بر روی یک سیستم استفاده می‌شوند و از آن‌ها در شبکه استفاده نمی‌شوند.

`<arpa/inet.h>`
حاوی توابعی برای دستکاری آدرس‌های IP عددی است.

`<netdb.h>`
حاوی توابعی برای ترجمه کردن اسامی پروتکل‌ها و میزبان‌ها به معادل عددی آنهاست.

ساختارها
--------

ساختارهای مختلفی برای کار با سوکت‌ها وجود دارد که یکی از ساده‌ترین آن‌ها ساختار sockaddr\_n است که در فایل netinet/in.h تعریف شده است:

```
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];
};

```

در ساختار sockaddr\_in، فیلدهای sin\_port و sin\_addr باید به صورت Network byte order باشند. چرا که این اطلاعات قرار است بر روی شبکه ارسال شوند و هر ماشینی هم روش مخصوص به خود را برای پردازش داده‌های موجود در این فیلدها را دارد و بنابراین این فیلدها باید به صورت استاندارد بر روی شبکه ارسال شوند تا از تداخل جلوگیری شود. به کمک توابع htons()‎ و htonl()‎ می‌توان تبدیل مورد نظر را انجام داد. همین‌طور دیگر فیلدها هم در صورتی‌که استفاده نمی‌شوند، باید با مقادیر ۰ پر شوند. این کار با استفاده از تابع memset()‎ قابل انجام است. این تابع در فایل سرآیند string.h تعریف شده‌است. به مثال زیر توجه کنید:

```
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

/\* pseudo code \*/

struct sockaddr\_in mystruct;

mystruct.sin\_family = AF\_INET;
mystruct.sin\_port = htons(1234);
mystruct.sin\_addr.s\_addr = htonl(INADDR\_ANY);

memset( &(mystruct.sinzero), '\0', 8 );

```

ساختار معروف دیگر ساختار addrinfo می‌باشد:

```
 struct addrinfo {
 int ai\_flags; /\* input flags \*/
 int ai\_family; /\* protocol family for socket \*/
 int ai\_socktype; /\* socket type \*/
 int ai\_protocol; /\* protocol for socket \*/
 socklen\_t ai\_addrlen; /\* length of socket-address \*/
 struct sockaddr \*ai\_addr; /\* socket-address for socket \*/
 char \*ai\_canonname; /\* canonical name for service location \*/
 struct addrinfo \*ai\_next; /\* pointer to next in list \*/
 };

```

توابع
-----

برخی از مهمترین توابع عبارتند از:

| نام | عملکرد |
| --- | --- |
| socket()‎ | یک سوکت از یک نوع خاص (مثلاً AF\_INET یا AF\_INET6) ایجاد می‌کند و منابع سیستم را به آن اختصاص می‌دهد و سپس یک توصیف‌گر فایل به آن سوکت برمی‌گرداند. |
| bind()‎ | این تابع معمولاً در سمت سرویس‌دهنده استفاده می‌شود و یک سوکت ایجاد شده توسط socket()‎ را به یک آدرس مرتبط می‌دهد. |
| listen()‎ | این تابع سوکت مورد نظر را آماده پذیرش اتصالات ورودی می‌کند و به این ترتیب سرویس‌گیرنده‌ها می‌توانند به سیستم متصل شوند. |
| connect()‎ | این تابع در سمت سرویس‌گیرنده استفاده می‌شود و یک شماره پورت تصادفی به سوکت اختصاص داده و سعی در برقراری ارتباط با سرویس‌دهنده می‌کند. |
| accept()‎ | در سمت سرویس‌دهنده استفاده می‌شود. این تابع یک اتصال راه دور که از طرف یک سرویس‌گیرنده آغاز شده را پذیرفته و یک سوکت جدید (برای هر اتصال) برمی‌گرداند که از طریق این سوکت جدید می‌توان با سرویس‌گیرنده ارتباط برقرار کرد. |
| send()‎ و recv()‎ | برای خواندن و نوشتن بر روی سوکت‌ها استفاده می‌شوند. از read()‎ و write()‎ هم می‌توان استفاده کرد اما send و recv مخصوص این کار نوشته شده‌اند. |
| close()‎ | سوکت را می‌بندد و منابع اختصاص داده شده به آن را آزاد می‌کند. |
|

### socket()‎

سوکت‌ها نقاط پایانی اتصالات هستند. این فراخوان سیستمی یک سوکت جدید ایجاد کرده و یک توصیف‌گر فایل به آن سوکت برمی‌گرداند. socket()‎ سه آرگومان دریافت می‌کند:

* domain: این آرگومان پروتکل مورد نظر را مشخص می‌کند. برای مثال:
	+ `AF_INET` برای سوکت‌های نوع IPv4
	+ `AF_INET6` برای سوکت‌های نوع IPv6
	+ `AF_UNIX` برای سوکت‌های محلی (از یک فایل استفاده می‌کند)
* type می‌تواند یکی از مقادیر زیر را بگیرد:
	+ `SOCK_STREAM` سوکت‌های قابل اطمینان و مبتنی بر جریان
	+ `SOCK_DGRAM` سوکت‌های دیتاگرام
	+ `SOCK_SEQPACKET`
	+ `SOCK_RAW`
* protocol این پارامتر پروتکل لایه انتقال را مشخص می‌کند. معمولاً مقدار این پارامتر با صفر مقدار دهی می‌شود تا پروتکل پیشفرض به صورت خودکار انتخاب شود.

این تابع در هنگام شکست مقدار ‎-۱ را برمی‌گرداند و در صورت موفقیت هم یک توصیف‌گر فایل به سوکت مورد نظر برمی‌گرداند که از نوع عدد صحیح است.

این تابع به شکل زیر تعریف شده است:

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

```

### bind()‎

این فراخوان سیستمی سوکت مورد نظر را به یک آدرس دلخواه مرتبط می‌کند. وقتی که یک سوکت جدید با socket()‎ ایجاد می‌شود، تنها نوع آن سوکت مشخص می‌شود. (IPv4 یا IPv6) و socket()‎ هیچ آدرسی به آن اختصاص داده نمی‌شود. قبل از اینکه سوکت بتواند برای پذیرش درخواست‌های سرویس‌گیرنده‌ها استفاده شود، باید با استفاده از bind()‎ یک آدرس دلخواه را به آن اتصال داد. bind()‎ سه آرگومان دریافت می‌کند.

* `sockfd` یک توصیف‌گر فایل به سوکتی که می‌خواهیم آدرسی را به آن مرتبط کنیم.
* `my_addr` اشاره‌گری به یک ساختار sockaddr که آدرس مورد نظر در آن قرار دارد.
* `addrlen` یک فیلد از نوع socklen\_t که اندازه ساختار sockaddr را مشخص می‌کند.

اگر سوکت با موفقیت به آدرس مورد نظر مرتبط شد، عدد ۰ و در غیر این صورت عدد ‎-۱ برمی‌گردد.

این تابع به شکل زیر اعلان شده است:

```
int bind(int sockfd, const struct sockaddr \*my\_addr, socklen\_t addrlen);

```

### listen()‎

بعد از اینکه یک آدرس دلخواه به سوکت اختصاص یافت (از طریق bind()‎)، فراخوان سیستمی listen()‎ سوکت مورد نظر را برای دریافت اتصالات ورودی آماده می‌کند تا کلاینتها بتوانند به سیستم متصل شوند. اتصالات ورودی به ترتیب در یک صف قرار می‌گیرند. listen()‎ به دو آرگومان احتیاج دارد:

* `sockfd` یک توصیف‌گر فایل به سوکت مورد نظر.
* `backlog` این آرگومان تعداد درخواست‌های در حال انتظار در صف را مشخص می‌کند.

وقتی که یک اتصال مورد پذیرش قرار گرفت، از صف حذف می‌شود تا فضای کافی برای اتصالات جدید به وجود آید. این تابع در هنگام موفقیت مقدار ۰ و در هنگام شکست مقدار ‎-۱ را برمی‌گرداند. این تابع به شکل زیر اعلان شده است:

```
int listen(int sockfd, int backlog);

```

### accept()‎

```
int accept(int sockfd, struct sockaddr \*cliaddr, socklen\_t \*addrlen);

```

برنامه‌هایی که بر روی سوکت خاصی در حال گوش فرادادن برای دریافت اتصالات کلاینت‌ها هستند (با استفاده از listen()‎)، می‌توانند با استفاده از فراخوان accept()‎ درخواست‌های کلاینت‌ها را پذیرفته و اتصال جدیدی را با آن‌ها برقرار کنند. فراخوان accept()‎ یکی از درخواست‌های موجود در صف listen()‎ را پذیرفته و یک توصیف‌گر پرونده جدید برمی‌گرداند که این توصیف‌گر پرونده، علاوه بر توصیف‌گری است که برنامه در حال گوش فرادادن به آن است. به این ترتیب، برنامه می‌تواند هم‌زمان از طریق یک توصیف‌گر درخواست‌های جدید را بپذیرد و برای ارتباط با هر کلاینت هم یک توصیف‌گر مجزا خواهد داشت. این فراخوان سه پارامتر دارد:

sockfd: توصیف‌گر سوکتی که برنامه از طریق آن مشغول گوش فرادادن به درخواست‌ها است.
cliaddr: اشاره‌گری به ساختار sockaddr به منظور دریافت کردن اطلاعات کلاینت
addrlen: اشاره‌گری به یک نوع socklen\_t که اندازه ساختاری که برای دریافت اطلاعت کلاینت ارسال شده (دومین پارامتر) را مشخص می‌کند. پس از اینکه تابع accept برگشت، این پارامتر تغییر می‌کند و مشخص می‌کند ک عملاً چند بایت از ساختار مورد نظر استفاده شده‌است.

مقدار برگشتی تابع accept در صورت موفقیت، یک توصیف‌گر پرونده است که برنامه از طریق آن می‌تواند با کلاینت ارتباط برقرار کند. تمامی ارتباطات با کلاینت از طریق این توصیف‌گر صورت خواهد گرفت. در صورت شکست، مقدار -۱ برمی‌گردد و متغیر سراسری errno هم با کد خطای مورد نظر مقداردهی می‌شود.

### connect()‎

```
int connect(int sockfd, const struct sockaddr \*serv\_addr, socklen\_t addrlen);

```

یک کلاینت با استفاده از این فراخوان می‌تواند به یک سرور متصل شود. این تابع در صورت موفقیت مقدار ۰ و در صورت شکست مقدار -۱ را برمی‌گرداند و متغیر errno را کد خطای مناسب مقداردهی می‌کند. اگر اجرای این تابع با شکست مواجه شود، برنامه‌های پورتابل می‌بایست بلافاصله توصیف‌گر پروندهٔ مورد استفاده را ببندند. چون این تضمین وجود ندارد که توصیف‌گر مورد نظر پس از شکست خوردن connect قابل استفادهٔ مجدد باشد. در استاندارد SUS وضعیت توصیف‌گر پرونده پس از شکست خوردن connect «تعریف‌نشده» توصیف شده‌است.

منابع
-----

مشارکت‌کنندگان ویکی‌پدیا. «Berkeley sockets». در *دانشنامهٔ ویکی‌پدیای انگلیسی*، بازبینی‌شده در ۱۳ ژوئیه ۲۰۱۳.