本例子是在unp官方代码的基础上改动的,完成的功能是客户端向服务器发送消息,并且在服务器上显示出来,服务器给予客户端确认信息。虽然说功能很简单,但是在这个过程中遇到了一些问题,还是值得一提的。而且,这个基础代码对于以后深入学习TCP/IP这一块还是很重要的。
下面进入正题。首先,在启动服务器和客户端进行通信之后,关闭终端常用的方法就是直接关闭窗口,然后,再次使用这个程序的时候,奇怪的事情就发生了。
错误:bind error: Address already in use
绑定失败地址已经被占用了。就是说,如果你不把进程杀了,休想再次使用这个地址了。怎么解决?重启电脑不就行了。确实也对,重启电脑通常是解决电脑问题的最简单但却很有效的方法。但是对于程序员而言,往往是打开很多软件和文档工作的,重启电脑是很烦的事情。为了查看到底是怎么一回事,被哪个进程占用了,敲一下netstat -tanlp看看哪个进程没退掉后台了,然后把进程杀了。其实也挺麻烦的,每次运行完都得去杀进程。还有一种比较靠谱的方法是,在blind或者listen之前添加两行代码,然后要关闭服务器进程时,使用CTRL+C而不是直接关闭窗口就行了。
int on = 1;
int ret = setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on) );
下面对整个过程进行分析。先看整个例子的示意图。
在客户端等待输入,用的是Fgets函数,读取的数据保存在buf指向的字符数组中,每次最多读取bufsize-1个字符(第bufsize个字符赋'\0'),如果文件中的该行,不足bufsize个字符,则读完该行就结束。如若该行(包括最后一个换行符)的字符数超过bufsize-1,则fgets只返回一个不完整的行,但是,缓冲区总是以NULL字符结尾,对fgets的下一次调用会继续读该行。读取输入信息之后,把客户端输入的信息写入套接字sockfd。
//等待输入
if(Fgets(sendline,MAXLINE,stdin) != NULL)
//写到套接字上
Writen(sockfd,sendline,strlen(sendline));
服务器方面,一直在监听客户端,从套接字中读取信息之后,将其在服务器端显示出来,用到的是Fputs,fputs是一种函数,具有的功能是向指定的文件写入一个字符串(不自动写入字符串结束标记符‘\0’)。成功写入一个字符串后,文件的位置指针会自动后移,函数返回为一个非负整数;否则返回EOF(符号常量,其值为-1)。接受到客户端信息之后,应该给予客户端确信信息。"I have receive the message!"
//读取套接字
if((receive=read(sockfd,recvline,MAXLINE))>0)
//在服务器上显示
Fputs(recvline,stdout);
//给与确认信息
sendline="I have receive the message!";
Writen(sockfd,sendline,MAXLINE);
客户端方面,也应该读取服务器发来确认信息,完成整个过程的通信。
//接收服务器的确认信息
Readline(sockfd,recvline,MAXLINE);
Fputs(recvline,stdout);
Fputs("\n",stdout);
下面给出完整代码。
/***********************************************************/
// 程序目的:TCP客户/服务器通信(服务器端)
// 日期: 2014-11-15
// 作者: spencer_chong
// 邮箱: zhuangxb91@qq.com
/***********************************************************/
#include "unp.h"
void message(int sockfd);
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);
//bind error: Address already in use
int on = 1;
int ret = setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on) );
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
Fputs("Welcome!",stdout);
Fputs("Waiting for connectting ......\n",stdout);
Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));
Listen(listenfd, LISTENQ);
for ( ; ; ) {
clilen = sizeof(cliaddr);
connfd = Accept(listenfd, (SA *) &cliaddr, &clilen);
Fputs("Successfully!\n",stdout);
if ( (childpid = Fork()) == 0) { /* child process */
Close(listenfd); /* close listening socket */
message(connfd);
exit(0);
}
Close(connfd); /* parent closes connected socket */
}
}
void message(int sockfd)
{
ssize_t receive = 0;
char recvline[MAXLINE];
char *sendline;
//读取套接字
if((receive=read(sockfd,recvline,MAXLINE))>0)
//在服务器上显示
Fputs(recvline,stdout);
//给与确认信息
sendline="I have receive the message!";
Writen(sockfd,sendline,MAXLINE);
}
/***********************************************************/
// 程序目的: TCP客户/服务器通信(客户端)
// 日期: 2014-11-15
// 作者: spencer_chong
// 邮箱: zhuangxb91@qq.com
/***********************************************************/
#include "unp.h"
void message(int sockfd);
int
main(int argc, char **argv)
{
int sockfd;
struct sockaddr_in servaddr;
if (argc != 2)
err_quit("usage: tcpcli <IPaddress>");
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);
Connect(sockfd, (SA *) &servaddr, sizeof(servaddr));
for(;;)
{
message(sockfd);
exit(0);
}
}
void message(int sockfd)
{
char sendline[MAXLINE],recvline[MAXLINE];
//等待输入
if(Fgets(sendline,MAXLINE,stdin) != NULL)
//写到套接字上
Writen(sockfd,sendline,strlen(sendline));
//接收服务器的确认信息
Readline(sockfd,recvline,MAXLINE);
Fputs(recvline,stdout);
Fputs("\n",stdout);
}
本文到此结束,欢迎留言纠错及讨论!