TCP客户/服务器实例
服务器程序
#include "unp.h"
int main(int argc, char **argv)
{
int listenfd, connfd;
pid_t childpid;
socklen_t clilen;
struct sockaddr_in cliaddr, servaddr;
listenfd = Socket(AF_INET, SOCK_STREAM, 0); //1
bzero(&servaddr, sizeof(servaddr)); //2
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); //3
servaddr.sin_port = htons(SERV_PORT);
Bind(listenfd, (SA*) &servaddr, sizeof(servaddr)); //4
Listen(listenfd, LISTENQ); //5
for(;;)
{
clilen = sizeof(cliaddr);
connfd = Accept(listenfd,(SA*) &cliaddr, &clilen); //6
if((childpid = Fork())== 0) //7
{
Close(listenfd);
str_echo(connfd);
exit(0);
}
Close(connfd); //8
}
}
客户端程序
#include "unp.h"
int main(int argc, char** argv)
{
int sockfd;
struct sockaddr_in servaddr;
if (argc != 2)
err_quit("usage:tcpcli<IP>");
sockfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
Inet_pton(AF_INET, argv[1], &servaddr.sin_addr); //9
Connect(sockfd, (SA*) &servaddr, sizeof(servaddr)); //10
str_cli(stdin, sockfd);
exit(0);
}
测试结果
1.先执行服务器端程序,此时服务器处于监听状态
2.再执行客户端程序。
客户端从标准输入获得内容,传递给服务器,又从服务器收回原数据,并显示在屏幕上。
3.当多个客户端同时连接服务器时,也可以照常工作
实例中使用的套接字介绍
socket函数
作用:
指定期望的通信协议类型(使用ipv4或ipv6 Tcp或Udp等)
使用:
#include<sys/socket.h>
int socket(int family, int type, int protocol); //若成功则返回非负描述符
参数介绍:
family参数为协议族,包括:
AF_INET IPv4协议
AF_INET6 IPv6协议
AF_LOCAL Unix域协议
AF_ROUTE 路由套接字
AF_KEY 密钥套接字
type参数指明套接字类型,包括:
SOCK_STREAM 字节流套接字
SOCK_DGRAM 数据报套接字
SOCK_SEQPACKET 有序分组套接字
SOCK_RAW 原始套接字
protocol参数应设为某个协议类型常值,包括:
IPPROTO_CP TCP传输协议
IPPROTO_UDP UDP传输协议
IPPROTO_SCTP SCTP传输协议
或者设为0,以选择给定family和type组合之后的默认值。
然而,并非所有套接字family和type的组合都是有效的。
然而TCP是一个字节流协议,仅支持SOCK_STREAM套接字。
connect函数
作用:
建立与TCP服务器的连接。
使用:
#include<sys/socket.h>
int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen); //若成功则返回非负
参数说明:
第一个参数为客户为参与通信自身建立起来的套接字描述符(socket函数返回的)。
第二个参数为指向服务器地址结构的指针,服务器地址结构必须包含服务器的IP地址和端口号。
第三个参数为该地址结构的大小。
按照TCP状态图,connect函数导致当前套接字从CLOSED状态(socket创建后即为该状态)转移到SYN_SENT状态
若成功则再转移到ESTABLISHED状态。若失败则该套接字不再可用。
bind函数
作用:
把本地协议地址赋予一个套接字。对于TCP,bind函数可以指定一个端口号,或指定一个IP,也可以两者都指定或两种都不指定。
使用:
#include<sys/socket.h>
int bind(int sockfd, const struct sockaddr* myaddr, socklen_t addrlen);
listen函数
作用:
仅由TCP服务器调用,把未连接的套接字转换成一个被动套接字,指示内核应接受向该套接字的连接请求,并规定了
套接字排队的最大连接个数。
使用:
#include<sys/socket.h>
int listen(int sockfd, int backlog);
accept函数
作用:
由TCP服务器调用,用于从已完成连接队列队头返回下一个已完成连接。如果已完成连接队列为空,那么进程进入睡眠。
使用:
#include<sys/socket.h>
int accept(int sockfd, struct sockaddr* cliaddr, socklen_t *addrlen);
参数说明:
clicaddr 和 addrlen用来返回已连接的对端进程(客户)的协议地址。
如果accept成功,那么其返回值是由内核自动生成的一个全新的描述符,代表与客户的TCP连接。在讨论accept函数
时,我们称它的第一个参数为监听套接字描述符(由socket创建,随后用做bind和listen的第一个参数的描述符),
称它的返回值为已连接套接字描述符。
一个服务器通常仅仅创建一个监听套接字,它在服务器的生命周期一直存在。内核为每个由服务器进程接受的客户连
接创建一个已连接套接字(TCP三次握手已经完成)。当服务器完成对某个给定客户的服务时,相应的已连接套接字
就被关闭。
如果我们对客户进程的协议地址不感兴趣,那么可以把cliaddr和addrlen均置为空指针。
fork和exec函数
使用:
#include<unistd.h>
pid_t fork(void);
fork理解最困难的地方在于调用它一次,它却返回两次。
它在调用进程(父进程)中返回一次,返回值是子进程的ID号。在子进程又返回一次,返回值是0。因此返回值
本身告知当前是子进程还是父进程。
父进程中调用fork之前打开的所有描述符在fork返回之后由子进程分享。我们看到网络服务器利用了这个特性:父
进程调用accept之后调用fork。所接受的已连接套接字随后就在父进程与子进程之间共享。
close函数
作用:
关闭套接字,终止TCP连接。
使用:
#include<unistd.h>
int close(int sockfd);
套接字地址结构
struct in_addr{
in_addr_t s_addr;
};
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(0);
};
当作为一个参数传递进任何套接字函数时,套接字地址结构总是以引用(指向该结构的指针)的方式来传递。然而以这样的指针作为参数之一的套接字必须处理来自所支持的任何协议族的套接字地址结构。
为了能够统一以指针的形式使用,使用时需要转换为通用套接字地址sockaddr,例如:
struct sockaddr_in serv;
/* fill in serv{} */
bind(sockfd, (struct sockaddr*) &serv, sizeof(serv));
地址转换函数
inet_pton 、 inet_ntop
使用:
#include<arpa/inet.h>
int inet_pton(int family, const char *strptr, void *addrptr);
const char *inet_ntop(int family, const void *addrptr, char *strptr, size_t len);
这两个函数的family参数既可以是AF_INET 也可以是AF_INET6.
第一个函数尝试转换strptr指针所指向的字符串,将点分十进制转化成二进制结果,存放在addrptr指向的内存中。
第二个函数进行相反的转换,len参数是目标存储单元的大小。
改动过的服务器测试程序
#include "unp.h"
int main(int argc, char **argv)
{
int listenfd, connfd;
const char *cp ="10.206.27.144";
pid_t childpid;
socklen_t clilen;
struct sockaddr_in cliaddr, servaddr;
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
inet_pton(AF_INET,cp,&servaddr.sin_addr);
servaddr.sin_port = htons(10000);
Bind(listenfd, (SA*) &servaddr, sizeof(servaddr));
Listen(listenfd, LISTENQ);
for(;;)
{
clilen = sizeof(cliaddr);
connfd = Accept(listenfd,(SA*) &cliaddr, &clilen);
if((childpid = Fork())== 0)
{
Close(listenfd);
str_echo(connfd);
exit(0);
}
Close(connfd);
}
}
改动过的客户端测试程序
#include "unp.h"
int main(int argc, char** argv)
{
int sockfd;
const char *cp = "10.206.27.144";
struct sockaddr_in servaddr;
sockfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(10000);
inet_pton(AF_INET,cp,&servaddr.sin_addr);
Connect(sockfd, (SA*) &servaddr, sizeof(servaddr));
str_cli(stdin, sockfd);
exit(0);
}