socket套接字编程教程

socket套接字工作于TCP/IP协议中应用层和传输层之间的一个抽象,使得网络上不同的计算机可以进行通信。

在客户端上建立套接字的步骤如下:

  • socket()创建套接字
  • connect()将套接字连接到服务器
  • 使用read()和write()发送和接收数据

在服务器端建立套接字的步骤如下:

  • socket()创建套接字
  • 使用bind()系统调用将套接字绑定到地址,地址由主机上的端口号组成。
  • listen()监听连接
  • accept()接受连接。此调用通常阻塞,直到客户端与服务器连接
  • 发送和接收数据

CS模式下API图
CS模式

服务端

sockfd和newsockfd文件描述符,这两个变量存储套接字系统call和accept系统调用返回的

struct sockaddr_in serv_addr,cli_addr;

sockaddr_in是互联网地址结构。定义在。变量serv_addr为服务器的地址,cli_addr是客户端的地址。

struct sockaddr_in
{
    short sin_family;
    u_short sin_port;
    struct in_addr sin_addr;
    char sin_zero [8];
};

in_addr结构,在相同的头文件中定义,只包含一个无符号长整型s_addr。

if (argc < 2) 
{
    fprintf(stderr,"ERROR, no port provided\n");
    exit(1);
}

用户需要传入服务器的端口号,若未执行此操作就报错

sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
    error("ERROR opening socket");

socket()创建一个套接字,它需要三个参数。
第一个是套接字的地址域。2种:共享文件系统的两个进程的unix域,以及Internet上任意两个主机的Internet域。符号常数AF_UNIX用于前者,而AF_INET后者

第二个参数是套接字的类型。2种:来自文件或管道的流套接字,以及以块读取消息的数据报套接字。符号常量是SOCK_STREAM和SOCK_DGRAM。

第三个参数是协议。如果此参数为零,操作系统将选择最适当的协议。它将选择TCP作为流套接字,UDP选择数据报套接字。

套接字调用返回文件描述符表的一个表项。此值用于对此套接字的所有后续引用。如果套接字调用失败,它返回-1。在这种情况下,程序显示和错误消息并退出。
上面是套接字调用的简化描述;还有许多其他选择域和类型,但这些是最常见的

bzero((char *) &serv_addr, sizeof(serv_addr));

该函数bzero()将缓冲区中的所有值设置为零。它有两个参数,第一个是指向缓冲区的指针,第二个是缓冲区的大小。因此,此行初始化 serv_addr为零。

portno = atoi(argv[1]);

服务器将侦听连接的端口号作为参数传入,并使用该atoi()函数将其从数字字符串转换为整数

serv_addr.sin_family = AF_INET;

short sin_family用于记录地址,它始终设置为符号常量AF_INET。

serv_addr.sin_addr.s_addr = INADDR_ANY;

unsigned long s_addr,记录主机的IP地址。对于服务器,这将始终是运行服务器的计算机的IP地址,INADDR_ANY是获得此地址的符号常量。

serv_addr.sin_port = htons(portno);

unsigned short sin_port,记录端口号。这里需要使用将主机字节顺序中的端口号转换为网络字节顺序中的端口号。

if (bind(sockfd, (struct sockaddr *) &serv_addr,
        sizeof(serv_addr)) < 0)
error("ERROR on binding");

bind()将套接字绑定到一个地址上。它需要三个参数:套接字文件描述符,绑定的地址和绑定的地址的大小。第二个参数是sockaddr类型,但传入的是sockaddr_in,因此必须将它转换为正确的类型。
bind()过程失败的原因有很多,最明显的是该套接字已经在这台机器上使用。

listen(sockfd,5);

listen(sockfd,5);
listen监听该套接字连接。第一个参数是套接字文件描述符,第二个参数是等待连接的队列大小

clilen = sizeof(cli_addr);
newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen);
if (newsockfd < 0)
    error("ERROR on accept");

accept()导致进程阻塞,直到客户端连接到服务器。当连接成功时,它唤醒该进程。返回一个新的文件描述符,并使用这个新的文件描述符完成此连接上的所有通信。第二个参数是指向连接另一端的客户端地址的引用指针,第三个参数是此结构的大小。

bzero(buffer,256);
n = read(newsockfd,buffer,255);
if (n < 0) error("ERROR reading from socket");
printf("Here is the message: %s\n",buffer);

使用bzero()函数初始化缓冲区,然后从套接字读取信息。read()使用新的文件描述符,阻塞直到有一些东西可读,即客户端已经执行write()后。它将读取套接字中的字符,并返回读取的字符数。

n = write(newsockfd,"I got your message",18);
if (n < 0) error("ERROR writing to socket");

一旦建立了连接,两端都可以读写。