在以下的客户端/服务器程序实例中,TCP_server.c的作用是接受client请求,并与client进行简单的数据通信,整体为一个阻塞式的网络聊天工具。
首先我们需要了解几个socket API:
int socket(int domain, int type, int protocol);
domian指协议族/域,IPV4采用AF_INET;
type是套接口类型,对于TCP采用SOCK_STREAM;
protocol一般取为0.成功返回一个小的非负整数值,与文件描述符类似int bind(int sockfd, const struct sockaddr* addr, socklen_t addrlen);
当 socket函数返回一个文件描述符时,只是存在于其协议族的空间中,并没有分配一个具体的协议地址(IPV4和端口号的组合),bind函数可以将一组固定的地址绑定到sockfd上。成功返回0,失败返回-1。sockfd是socket函数返回的描述符;
addr指定了想要绑定的IP和端口号,均要使用网络字节序-即大端模式;
addrlen是前面struct sockaddr(与sockaddr_in等价)的长度。
struct sockaddr_in {
short int sin_family; /* 地址族 */
unsigned short int sin_port; /* 端口号 */
struct in_addr sin_addr; /* Internet地址 */
unsigned char sin_zero[8]; /* 与struct sockaddr一样的长度 */
};int listen(int sockfd, int backlog);
listen声明sockfd处于监听状态,并且最多允许backlog个客户处于连接状态,该参数至少为1,一般设置为5;成功返回0,失败返回-1int accept(int sockfd, struct sockaddr* addr, socklen_t* addrlen);
accept有服务器调用,第二个参数为输出型参数,第三个为输入输出型。int connect(int sockfd, const struct sockaddr* addr, socklen_t addrlen);
通过connect函数建立于TCP服务器的连接,实际是发起三次握手过程,仅在连接成功或失败后返回。参数sockfd是本地描述符,addr为服务器地址,addrlen是socket地址长度。int addr_t inet_addr(const char *cp);
该函数是把字符串形式的点分十进制转化成32bit的整数char *inet_ntoa(struct in_addr in);
该函数将32bit的整数转化成点分十进制
1、单进程TCP服务器
tcp_server.c
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<stdlib.h>
#include<netinet/in.h>
#include<string.h>
//./tcp_server[local_ip][local_port]
static void usage(const char* proc)
{
printf("Usage:%s[server_ip][server_port]\n",proc);
}
int main(int argc, char* argv[])
{
if(argc != 3){
usage(argv[0]);
return 1;
}
int listen_sock = socket(AF_INET,SOCK_STREAM,0);
if(listen_sock < 0)
{
perror("socket");
exit(2);
}
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_port = htons(atoi(argv[2]));
server.sin_addr.s_addr = inet_addr(argv[1]);
if(bind(listen_sock,(struct sockaddr*)&server,sizeof(server))<0)
{
perror("bind");
exit(3);
}
if(listen(listen_sock,5)<0)
{
perror("listen");
exit(4);
}
printf("server bind and listen success, waiting accept...\n");
while(1)
{
struct sockaddr_in client;
socklen_t len = sizeof(client);
int new_sock = accept(listen_sock,(struct sockaddr*)&client,&len);
if(new_sock < 0)
{
perror("accept");
continue;
}
printf("get a new
client...,%s:%d\n",inet_ntoa(client.sin_addr),
ntohs(client.sin_port));
char buf[1024];
while(1)
{
ssize_t r = read(new_sock,buf,sizeof(buf)-1);
if(r > 0)
{
buf[r] = 0;
printf("client:%s\n",buf);
write(new_sock,buf,strlen(buf));
}
else if(r == 0){
close(new_sock);
printf("client quit...\n");
break;
}
else
{
perror("read");
close(new_sock);
break;
}
}
}
return 0;
}
#include
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
// ./client server_ip server_port
int main(int argc,char* argv[])
{
if(argc != 3)
{
printf("please input:%s server_ip server_port...\n",argv[0]);
return 1;
}
int sock = socket(AF_INET,SOCK_STREAM,0);
if(sock < 0)
{
perror("sock");
exit(2);
}
struct sockaddr_in client;
client.sin_family = AF_INET;
client.sin_port = htons(atoi(argv[2]));
client.sin_addr.s_addr = inet_addr(argv[1]);
if(connect(sock,(struct sockaddr*)&client,sizeof(client))<0)
{
perror("connect");
exit(3);
}
printf("connect server success...\n");
char buf[10240];
while(1)
{
printf("please Enter#: ");
fflush(stdout);
ssize_t r = read(0,buf,sizeof(buf)-1);
if(r > 0)
{
buf[r-1] = 0;
write(sock,buf,strlen(buf));
r = read(sock,buf,sizeof(buf)-1);
if(r > 0)
{
buf[r] = 0;
printf("server echo#: %s\n",buf);
}
}
}
return 0;
}
2、多进程版本
将tcp_server.c部分内容修改:
tcp_forkserver.c:
53 printf("get a new client...,%s:%d\n",inet_ntoa(client.sin_addr),
54 ntohs(client.sin_port));
55
56 pid_t id=fork();
57 if(id<0)
58 {
59 close(new_sock);
60 }
61 else if(id==0)
62 {
63 close(listen_sock);
64 if(fork()>0)
65 {
66 exit(0);
67 }
68 while(1)
69 {
70 char buf[1024];
71 ssize_t r = read(new_sock,buf,sizeof(buf)-1);
72 if(r > 0)
73 {
74 buf[r] = 0;
75 printf("client:%s\n",buf);
76 write(new_sock,buf,strlen(buf));
77 }
78 else if(r == 0){
79 close(new_sock);
80 printf("client quit...\n");
81 break;
82 }
83 else
84 {
85 perror("read");
86 close(new_sock);
87 break;
88 }
89 }
90 close(new_sock);
91 }
92 else{
93 close(new_sock);
94 }
95 }
96
97 return 0;
98 }
3、多线程版本:
将原来的tcp_server.c 中内容部分修改到
tcp_threadserver.c:
1.添加handler函数
void* handler(void* arg)
{
int new_sock=(int)arg;
while(1)
{
char buf[1024];
ssize_t r = read(new_sock,buf,sizeof(buf)-1);
if(r > 0)
{
buf[r] = 0;
printf("client:%s\n",buf);
write(new_sock,buf,strlen(buf));
}
else if(r == 0){
close(new_sock);
printf("client quit...\n");
break;
}
else
{
perror("read");
close(new_sock);
break;
}
}
}
2.修改main函数中以下部分:
printf("get a new client...,%s:%d\n",inet_ntoa(client.sin_addr),
ntohs(client.sin_port));
pthread_t id;
pthread_create(&id,NULL,handler,(void*)new_sock);
pthread_detach(id);
}
return 0;
}
在以上程序中我们先后启动服务器端和客户端程序,然后按CTRL+C终止掉服务器端程序,这时再运行服务器端程序,就会出现绑定的错误。这是因为,虽然服务器端的应用程序终止了,但是TCP协议层的连接并没有完全断开,服务器是主动断开连接的一方,所以在TIME_WAIT期间仍然不能再次监听同样的服务器端口。