《Linux C一站式学习》之Socket编程(tcp) 这是一篇用linux c 库 的socket API实现TCP通信的文章,如果想查看原文请点
下图是基于TCP协议的客户端/服务器程序的一般流程:
图一 TCP协议通信流程
解析:
1.TCP连接建立过程:
1)服务器调用socket()、bind()、listen()完成初始化后,调用accept()阻塞等待,处于监听端口的状态,
2)客户端调用
socket()初始化后,调用connect()发出SYN段并阻塞等待服务器应答,
3)服务器应答一个SYN-ACK段,客户端收到后从
connect()返回,同时应答一个ACK段,
4)服务器收到后从accept()返回
5)到此完成TCP连接
2.数据传输过程:
1)连接后,TCP协议提供全双工的通信服务,但是一半的CS程序是client请求,server应答。
2)我们可以想象连接服务器与客户端的是两条管道, 这两条管道是同时打开同时关闭的。read与write函数的调用情况与操作管道的read与write函数很相像(我指的是进程阻塞和管道关闭的情况)。
3)当客户端调用close()关闭连接,就像写端关闭管道一样,服务器的read()返回0,这样服务器就知道客户端关闭连接了,
4)如果一方调用shutdown()则连接处于半关闭状态,任然可以接受对方发来的数据
下面的例子很简单,源代码可以点
这里下载,我尽量在注释将源码解析清楚,最后会加上一点总结。
server.c的作用是接收客户端发送过来的字符串,然后将字符的大写返回给客户端。
/*server.c*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define MAXLINE 80
#define SERV_PORT 8000
int main(void)
{
struct sockaddr_in servaddr, chiaddr; //struct sockaddr_in结构表示IPv4地址格式
socklen_t cliaddr_len;
int listenfd, connfd; //文件描述符
char buf[MAXLINE];
char str[INET_ADDRSTRLEN];
int i, n;
/*
socket()打开一个网络通讯端口。 对于IPv4,family参数指定为AF_INET。 对于TCP协议,type参数指定为SOCK_STREAM,表示面向流的传输协议。 如果是UDP
协议,则type参数指定为SOCK_DGRAM,表示面向数据报的传输协议。 protocol参数的介绍从略,指定为0即可。 */
listenfd = socket(AF_INET, SOCK_STREAM,0);
/*
给servaddr赋值,其中htonl()与htons() 是用于网络字节序和主机字节序的转换。
首先将整个结构体清零, 然后设置地址类型为AF_INET, 网络地址为INADDR_ANY,这个宏表示本地的任意IP地址,因为服务器可能有多个网卡,
每个网卡也可能绑定多个IP地址,这样设置可以在所有的IP地址上监听,直到与某个客户端建立了连接时才确定下来到底用哪个IP地址, 端口号为
SERV_PORT,我们定义为8000。 */
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
/*
bind()的作用是将参数sockfd和myaddr绑定在一起,使sockfd这个用于网络通讯的文件描述符监听myaddr所描述的地址和端口号。 */
bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
/*
listen()声明sockfd处于监听状态,并且最多允许有backlog(这里是20)个客户端处于连接待状态,如果接收到更多的连接请求就忽略。listen()成功返回0,失败返回-1。 */
listen(listenfd, 20);
printf("Accepting connections ... \n");
while (1)
{
cliaddr_len = sizeof(chiaddr);
/* 三方握手完成后,服务器调用accept()接受连接, 如果服务器调用accept()时还没有客户端的连接请求,就阻塞等待直到有客户端连接上来。 accept()的参数listenfd是先前的监听文件描述符, 而accept()的返回值是另外一个文件描述符connfd,之后与客户端之间就通过这个connfd通讯,最后关闭connfd断开连接,而不关闭listenfd
*/
connfd = accept(listenfd, (struct sockaddr*)&chiaddr, &cliaddr_len);
n = read(connfd, buf, MAXLINE);
printf("receive form %s at Port %d \n",
inet_ntop(AF_INET, &chiaddr.sin_addr, str, sizeof(str)), ntohs(chiaddr.sin_port));
for (i=0; i<n; i++)
buf[i] = toupper(buf[i]);
write(connfd, buf, n);
close(connfd);
}
}
|
client.c的作用是从命令行参数中获得一个字符串发给服务器,接收服务器发回来的字符串并打印。
/* client.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define MAXLINE 80
#define SERV_PORT 8000
int main(int argc, char *argv[])
{
struct sockaddr_in servaddr;
char buf[MAXLINE];
int sockfd, n;
char *str;
if (argc !=2)
{
fputs("usage:./client message \n", stderr);
exit(1);
}
str = argv[1];
/*打开一个网络通信端口*/
sockfd = socket(AF_INET, SOCK_STREAM, 0);
/*设置服务器地址和端口号*/
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
servaddr.sin_port = htons(SERV_PORT);
/*链接服务器*/
connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
write(sockfd, str, strlen(str));
n = read(sockfd, buf, MAXLINE);
printf("Response from the serve:\n");
write (STDOUT_FILENO, buf, n);
close(sockfd);
return 0;
}
|
操作:
将源代码下载下来之后执行以下命令:
gcc -o server -g server.c
gcc -o client -g client.c
./server
(在另外一个终端cd到client执行文件所在文件夹)
./client xxx
结果应该是屏幕打印XXX并退出。
总结:最简单的TCP socket编程的一般步骤应该是这样的
服务器端:
1)调用
int socket(int family, int type, int protocol) 来打开一个网络通信端口,
2)调用 int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen) ,绑定一个固定的网络地址和端口号,其中网络地址由myaddr设定。
备注:bind()的作用是将参数sockfd和myaddr绑定在一起,使sockfd这个用于网络通讯的文件描述符监听myaddr所描述的地址和端口号。
3)调用 int listen(int sockfd, int backlog), 设置最多允许backlog个端口处于链接状态
4)调用 int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen), 接受连接。
备注:如果accept成功返回,服务器通过sockfd文件描述符与客户端通信(用read/write)
客户端:
1)调用
int socket(int family, int type, int protocol) 来打开一个网络通信端口,
2)设置好服务器地址与端口号,调用 int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen) 发起连接。
备注:如果连接成功,客户端将会用sockfd文件描述符与服务器通信。
注意:任何一方关闭sockfd文件描述符都将使得通信断开。
阅读(193) | 评论(0) | 转发(0) |