首页 > 学技术 > 技术网文 > C/C++ > 正文

[保留] 一个网络编程中send的菜鸟问题


来源 chinaunix.net kuqin整理

刚接触linux网络编程,碰到一些烦人的小问题,比如说写一个测试程序,写了一个客户端和服务器端的程序,实现功能很简单,客户端发送一个字符串给服务器断,服务器断将这个字符串处理一下然后返回给客户端,在我建立了连接之后在客户端用send()之后发现服务器断接收不到字符串,后来测试了半天我在send之后马上关掉连接的socket文件sockfd服务器端马上就收到了,我想问一下是不是每次send之后一定要关掉连接的sockfd才能再服务器端才能接收到客户端发送的字符串的??如果是这样我要再后面的程序中接收来自服务器端回送的字符串之后应该怎么办呢?总不能说我重新建立一个连接吧.......郁闷中.



 linyue 回复于:2006-12-26 16:41:25

send以后flush一下缓冲区
fflush()


 pineapple1175 回复于:2006-12-26 17:17:27

兄弟,还是不行啊,谁和我说说,郁闷死了要


 mq110 回复于:2006-12-26 17:18:34

贴代码.


 pineapple1175 回复于:2006-12-26 17:50:51

客户端:
#include<stdio.h>

#include<stdlib.h>

#include<unistd.h>

#include<sys/socket.h>

#include<arpa/inet.h>

#include<netinet/in.h>

#include<sys/types.h>

#include<sys/stat.h>

#include<string.h>

#include<sys/time.h>



#define PORT 9877

#define BUFLEN 1024

#define MAXLEN 128

#define MAXSLEEP 128





int connect_retry(int sockfd,struct sockaddr *addr,socklen_t alen);

void printbuf(int sockfd);

void getbuf(int sockfd,char *buf);



int main(int argc,char *argv[])

{

int sockfd;

struct sockaddr_in serveraddr;

int addrlen;

char *str="hello";

pid_t pid;

/*char buf[BUFLEN];*/

if(argc!=2)

{

printf("usage:clienttest ipaddress\n");

exit(1);

}

if( (sockfd=socket(AF_INET,SOCK_STREAM,0))<0 )

{

perror("create socket error");

exit(1);

}

bzero(&serveraddr,sizeof(serveraddr));

serveraddr.sin_family=AF_INET;

serveraddr.sin_port=htons(PORT);

inet_pton(AF_INET,argv[1],&serveraddr.sin_addr);

addrlen=sizeof(struct sockaddr);

if( connect_retry(sockfd,(struct sockaddr *)&serveraddr,addrlen)<0 )

{

printf("connect to server error\n");

exit(1);

}

if(send(sockfd,str,strlen(str),0)<0)

{

perror("send str to server error");

exit(1);

}
fflush(sockfd);

printbuf(sockfd);

close(sockfd);

exit(0);

}





/*

//connect_retry

*/

int connect_retry(int sockfd,struct sockaddr *addr,socklen_t alen)

{


int nsec;

for(nsec=1;nsec<MAXSLEEP;nsec<<=1)

{

if( connect(sockfd,addr,alen)==0 )

return 0;

if(nsec<=MAXSLEEP/2)

sleep(nsec);

}

return (-1);

}





/*

//printbuf

*/

void printbuf(int sockfd)

{

char buf[BUFLEN];

fd_set rfd;

struct timeval timeout;

timeout.tv_sec=10;

timeout.tv_usec=0;

FD_ZERO(&rfd);

FD_SET(sockfd,&rfd);

switch(select(sockfd+1,&rfd,NULL,NULL,&timeout))

{

case -1:

perror("select error");

exit(1);

case 0:

printf("receive data from server timeout");

break;

default:

getbuf(sockfd,buf);

printf("Get string from server: ");

printf("%s",buf);

break;

}

printf("\n"); 

}





/*

//getbuf

*/

void getbuf(int sockfd,char *buf)

{

char tempbuf[MAXLEN];

int i,n,index;

n=0;

index=0;

while( (n=recv(sockfd,tempbuf,MAXLEN,0))>0 )

{

for(i=0;i<n;i++)

buf[index++]=tempbuf;

}

buf[index]='\0';

if(n<0)

{

perror("recv data from server error");

exit(1);

}

}




服务器端:
#include<stdio.h>

#include<stdlib.h>

#include<unistd.h>

#include<sys/socket.h>
#include<sys/wait.h>

#include<arpa/inet.h>

#include<netinet/in.h>

#include<string.h>



#define PORT 9877

#define BUFLEN 1024

#define MAXLEN 128

#define BACKLOG 10



void getbuf(char *buf,int sockfd);

void reversebuf(char *buf);



/*

//main

*/

int main(int argc,char *argv[])

{

/*init serversockfd,clientsockfd;*/

int listenfd,connectfd;

int clientaddrlen;

int status;

struct sockaddr_in clientaddr,serveraddr;

char buf[BUFLEN];

char *welcome="Connect to server successful.Welcome!!!\n";

pid_t pid;

if( (listenfd=socket(AF_INET,SOCK_STREAM,0))<0 )

{

/*create socket if failure exit*/

perror("create socket error!");

exit(1);

}

/*init serveraddr*/

bzero(&serveraddr,sizeof(serveraddr));

serveraddr.sin_family=AF_INET;

serveraddr.sin_port=htons(PORT);

serveraddr.sin_addr.s_addr=htonl(INADDR_ANY);

/*bind port and serveraddr to listenfd*/

if( bind(listenfd,(struct sockaddr *)&serveraddr,sizeof(struct sockaddr))<0 )

{

perror("bind port failure!");

exit(1);

}

if( listen(listenfd,BACKLOG)<0 )

{

perror("listen error");

exit(1);

}

for(;;)

{

clientaddrlen=sizeof(clientaddr);

if( (connectfd=accept(listenfd,(struct sockaddr *)&clientaddr,&clientaddrlen))<0 )

{

perror("accept from client failure!");

exit(1);

}
printf("Received client: %s request\n",inet_ntoa(clientaddr.sin_addr));

if( (pid=fork())<0 )

{

perror("fork error");

exit(1);

}

else if( pid==0 )

{
getbuf(buf,connectfd);

printf("Get client string: %s\n",buf);
if( send(connectfd,welcome,strlen(welcome),0)<0 )

{

perror("send welcome failure");

exit(1);

}
reversebuf(buf); /*reverse buf*/

if( send(connectfd,buf,strlen(buf),0)<0 ) /*send reversed string to client*/

{

perror("send reversed string error");

exit(1);

}
close(connectfd);
exit(0);

}
close(connectfd);

}

close(connectfd);

close(listenfd);

exit(0);

}





/*

//getbuf

*/

void getbuf(char *buf,int sockfd)

{

char tempbuf[MAXLEN];

int n;

int i,index;

index=0;

n=0;

while( (n=recv(sockfd,tempbuf,MAXLEN,0))>0 )

{

for(i=0;i<n;i++)

buf[index++]=tempbuf;

}

buf[index]='\0';

if(n<0)

{

perror("recv error");

exit(1);

}

}





/*

//reversebuf

*/

void reversebuf(char *buf)

{

char c;

int i,len;

len=strlen(buf);

int mid=(int)len/2;

for(i=0;i<mid;i++)

{

c=buf;

buf=buf[len-1-i];

buf[len-1-i]=c;

}

}


 linyue 回复于:2006-12-26 20:30:13

我晕,你server的程序里那个getbuf函数里有个while循环不停recv,而你用的又是阻塞模式,所以你收到第一个字符串"hellp"以后,recv又阻塞在while那儿了,当然看上去就跟没受到东西一样了,其实已经收到了,这是程序设计的问题…


 cjaizss 回复于:2006-12-26 20:36:43

首先,我真的很不相信你编译的时候连个warning都可以。却对warning视而不见


 linyue 回复于:2006-12-26 20:41:09

引用:原帖由 cjaizss 于 2006-12-26 20:36 发表
首先,我真的很不相信你编译的时候连个warning都可以。却对warning视而不见 



其次在哪儿呢?- -。。。
他的问题跟那个warning一点关系也没有,那个warning是他用fflush的时候给错参数了,应该给个FILE,他给了个文件描述符。其实不用fflush..


 langue 回复于:2006-12-26 20:44:26

引用:原帖由 linyue 于 2006-12-26 20:41 发表


其次在哪儿呢?- -。。。
他的问题跟那个warning一点关系也没有,那个warning是他用fflush的时候给错参数了,应该给个FILE,他给了个文件描述符。其实不用fflush.. 



嗯。如果必须,可以用 fsync


 cjaizss 回复于:2006-12-26 20:50:48

引用:原帖由 langue 于 2006-12-26 20:44 发表


嗯。如果必须,可以用 fsync 


楼主程序的问题不是出在这里


 linyue 回复于:2006-12-26 20:55:31

引用:原帖由 cjaizss 于 2006-12-26 20:50 发表

楼主程序的问题不是出在这里 



人家也没说出在这里啊,而且我都说了出在哪儿了啊……ft,你怎么老跟别人较劲阿……admire


 cjaizss 回复于:2006-12-26 20:57:25

那我就说说LZ的愿意吧,LZ在那里阻塞等待128字节。
可是人家就发了5个字节。
所以服务器一直阻塞在recv上,直到客户端关闭,recv调用返回。


 linyue 回复于:2006-12-26 21:03:48

引用:原帖由 cjaizss 于 2006-12-26 20:57 发表
那我就说说LZ的愿意吧,LZ在那里阻塞等待128字节。
可是人家就发了5个字节。
所以服务器一直阻塞在recv上,直到客户端关闭,recv调用返回。 



admire…………你这完全错误啊……
首先,strlen("hello") == 6而不是你说的5
其次,他while里第一次的recv已经收到了那6个字节,并没有产生能让人观察到的阻塞。但是它的while循环又执行了一遍recv(因为第一次recv的返回值>0),这第二次recv才产生了他所观察到的阻塞……
你说什么等待128字节根本就不对……


 langue 回复于:2006-12-26 21:14:06

引用:原帖由 linyue 于 2006-12-26 21:03 发表
admire…………你这完全错误啊……
首先,strlen("hello") == 6而不是你说的5
其次,他while里第一次的recv已经收到了那6个字节,并没有产生能让人观察到的阻塞。但是它的while循环又执行了一遍recv(因为第一次recv的返回值>0),这第二次recv才产生了他所观察到的阻塞……
你说什么等待128字节根本就不对…… 



'h'
'e'
'l'
'l'
'o'
'\0'

其中,strlen(3) 在计算字符串长度的时候只算前面的五个字符,'\0' 没有算进去。

src/lib/libc/string/strlen.c:

引用:size_t
strlen(str)
        const char *str;
{
        register const char *s;

        for (s = str; [color=Red]*s[/color]; ++s);
        return(s - str);
}



用红色标出的代码部分,表示对 s 进行反引用,判断到 *s 为非零就是跳出并终止 for 循环的条件。这个时候,'\0' 没有算进去。


 linyue 回复于:2006-12-26 21:17:53

引用:原帖由 langue 于 2006-12-26 21:14 发表


'h'
'e'
'l'
'l'
'o'
'\0'

其中,strlen(3) 在计算字符串长度的时候只算前面的五个字符,'\0' 没有算进去。

src/lib/libc/string/strlen.c:



用红色标出的代码部分,表示对 s 进行反引用,判 ... 




不过不影响这个帖子的解决……
今天脑子进水了ft...


 cjaizss 回复于:2006-12-26 21:25:38

哎,我脑子也进水了....
描述错误
人家发了5个字节在那里。
然后自己就recv返回了,然后人家再就没发数据了。
此时协议栈内缓冲区里已经没有数据了,但是人家又没有关闭,recv不好返回。终于人家关闭了连接,recv终于好返回了0代表人家已经关闭连接


 langue 回复于:2006-12-26 21:28:03

[font="Courier New"]recv(2):

     [color=Red]If no messages are available at the socket, the receive call waits for a
     message to arrive, unless the socket is nonblocking (see fcntl(2))  in
     which case the value -1 is returned and the external variable errno set
     to EAGAIN.
[/color] [color=Blue]The receive calls normally return any data available, up to
     the requested amount, rather than waiting for receipt of the full amount
     requested; this behavior is affected by the socket-level options
     SO_RCVLOWAT and SO_RCVTIMEO described in getsockopt(2).
[/color][/font]

这段文字,前一部分是楼主问题所在,后一部分是 15 楼 linyue 指出的。


 cjaizss 回复于:2006-12-26 21:35:20

阻塞的read(recv同理)不一定每次都会返回所希望读到的字节数,然而对与TCP,阻塞I/O时没有数据可读是不可返回的,除非对方主动关闭返回0。偶有的时候写设备驱动的时候read返回0不代表任何意思,只代表当前没有数据。然而对于TCP的这些I/O是一个标准


 langue 回复于:2006-12-26 21:38:41

引用:原帖由 cjaizss 于 2006-12-26 21:35 发表
阻塞的read(recv同理)不一定每次都会返回所希望读到的字节数,然而对与 TCP,阻塞I/O时没有数据可读是不可返回的,除非对方主动关闭返回0。偶有的时候写设备驱动的时候read返回0不代表任何意思,只代表当前没有数据。然而对于TCP的这些I/O是一个标准  



对 socket descriptors 进行 close(),只是半关闭。返回 0,代表 EOF。两者联系起来,就不难理解了。:)


 pineapple1175 回复于:2006-12-26 22:26:32

才出去看了两个小时的书就有这么多回帖了,真是感激啊,再这之前的另外一个帖子上发了半天到现在也没人来理我:(
我去试了一下把while循环去掉直接send了一下就直接用recv接收就能正常运行了,不过apue2上面也明确说了: Since we're using a SOCK_STREAM socket, we can't be guaranteed that we will read the entire string in one call to recv, so we need to repeat the call until it returns 0.我那段while循环也是参照apue2上面的,假如我发送比较多的内容的话还是要用这种模式来接收的,还有就是nonblocking这种状态和blocking这种状态到底有什么区别??如果我非要用while循环接收那么代码要怎么改呢???


 pineapple1175 回复于:2006-12-26 22:27:21

真感谢大家的回复,受益非浅啊,谢谢谢谢


 pineapple1175 回复于:2006-12-26 22:33:58

引用:原帖由 linyue 于 2006-12-26 20:30 发表
我晕,你server的程序里那个getbuf函数里有个while循环不停recv,而你用的又是阻塞模式,所以你收到第一个字符串"hellp"以后,recv又阻塞在while那儿了,当然看上去就跟没受到东西一样了,其实已经收到 ... 



哈哈,其实我原本来是没用fflush的因为刚看了书说用这函数能把缓冲里的数据送到kernal里面,一开始以为数据没真正发送出去,于是就用了,至于warning当时看到了,但没心情卡管那个了,^_^


 pineapple1175 回复于:2006-12-26 22:42:07

引用:原帖由 linyue 于 2006-12-26 20:30 发表
我晕,你server的程序里那个getbuf函数里有个while循环不停recv,而你用的又是阻塞模式,所以你收到第一个字符串"hellp"以后,recv又阻塞在while那儿了,当然看上去就跟没受到东西一样了,其实已经收到 ... 



用了recv第一次接收了5个字节之后第二次还会接收时返回值还会>0吗??如果是的话就代表还有数据可以接收啊?这和blocking状态有关??


 kuaizaifeng 回复于:2006-12-27 10:46:17

引用:原帖由 pineapple1175 于 2006-12-26 22:26 发表
才出去看了两个小时的书就有这么多回帖了,真是感激啊,再这之前的另外一个帖子上发了半天到现在也没人来理我:(
我去试了一下把while循环去掉直接send了一下就直接用recv接收就能正常运行了,不过apue2上面也明确说 ... 




接着往下看那本书
后面应该就讲了如何读取或者发送一段期望长度的数据




原文链接:http://bbs.chinaunix.net/viewthread.php?tid=876489
转载请注明作者名及原文出处



收藏本页到: