1.1 一个简单的客户端程序
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<string.h>
#define MAXLINE 512
int main(int argc, char **argv)
{
int sockfd, n;
char recvline[MAXLINE + 1] = {0};
struct sockaddr_in servaddr;
if (argc != 3)
{
printf("ivalid input.\n");
return -1;
}
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
printf("socket error.\n");
return -1;
}
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(atoi(argc[2]));
if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0)
{
printf("inet_pton error for %s.\n", argv[1]);
return -1;
}
if (connect(sockfd, (sockaddr *)&servaddr, sizeof(servaddr)) < 0)
{
printf("connect error.\n");
return -1;
}
while ((n = read(sockfd, recvline, MAXLINE) > 0)
{
printf("recv: %s\n");
}
if (n < 0)
{
printf("read error.\n");
return -1;
}
}
创建TCP套接字
socket函数创建一个网际字节流套接字,该函数返回一个小整数描述符,以后所有函数调用就用该描述符来标识这个套接字。
指定服务器的IP地址和端口
我们把服务器的IP地址和端口号填入一个网际套接字地址结构(一个名为servaddr的sockaddr_in结构变量)。使用bzero把整个结构清零后,置地址族为AF_INET。网际套接字地址结构中IP地址和端口号这两个成员必须使用特定格式,为此我们调用库函数htons(“主机到网络短整数”)去转换二进制端口号,又调用库函数inet_pton(”呈现形式到数值”)去把ASCII命令行参数(例如192.168.1.1)转换为合适的格式。
建立与服务器的连接
connect函数应用于一个TCP套接字时,将与由它的第二个参数指向的套接字地址结构指定的服务器建立一个TCP连接。该套接字地址结构的长度也必须作为该函数的第三个参数指定,对于网际套接字地址结构,我们总是使用C语言的sizeof操作符由编译器来计算这个长度。
读入并输出服务器的应答
我们使用read函数读取服务器的应答,并用标准的I/O函数输出结果。
1.2 一个简单的服务器程序
#include<iostream>
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<string.h>
#define MAXLINE 512
int main(int argc, char **argv)
{
int listenfd, connfd;
struct sockaddr_in servaddr;
char buff[MAXLINE];
if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
printf("socket error.\n");
return -1;
}
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(8080);
if (bind(listenfd, (sockaddr *)&servaddr, sizeof(servaddr)) < 0)
{
printf("bind socket failed.\n");
return -1;
}
if (listen(listenfd, 5) < 0)
{
printf("listen socket failed.\n");
return -1;
}
for (;;)
{
if ((connfd = accept(listenfd, (sockaddr *)NULL, NULL)) < 0)
{
printf("accept failed.\n");
}
else
{
sprintf(buff, "[0x%x] hello world.\r\n", connfd);
write(connfd, buff, strlen(buff));
}
close(connfd);
}
return 0;
}
创建TCP套接字
TCP套接字的创建与客户端程序相同
把服务器的端口捆绑到套接字
通过填写一个网际套机子地址结构并调用bind函数,服务器的端口被捆绑到所创建的套接字。我们指定IP地址为INADDR_ANY,这样要是服务器有多个网络接口,服务器进程就可以在任意网络接口上接受客户连接。
把套接字转换成监听套接字
调用listen函数把该套接字转换成一个监听套接字,这样来自客户的外来连接就可以在该套接字上由内核接受。socket、bind和listen这3个调用步骤是任何TCP服务器准备所谓的监听描述符的正常步骤。
接受客户连接,发送应答
通常情况下,服务器进程在accept调用中被投入睡眠,等待某个客户连接的到达并被内核接受。TCP连接使用所谓的三次握手来建立连接。握手完毕后accept返回,其返回值是一个称为已连接描述符的新描述符。该描述符用于与新连接与新近连接的那个客户通信。accept为每个连接到本服务器的客户返回一个新描述符。
终止连接
服务器通过调用close关闭与客户的连接。该调用引发正常的TCP连接终止序列:每个方向上发送一个FIN,每个FIN又由各自的对端确认。后续详细讲述TCP的三次握手和用于终止一个TCP连接的4个TCP分组。