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);
Unix网络编程之基本TCP套接字编程(上),布布扣,bubuko.com