【学习笔记】天嵌2440第三季下学期——linux tcp网络编程

来源:互联网 发布:软件行业的进项 编辑:程序博客网 时间:2024/06/07 20:02

tcp网络编程模型:

客户端的流程:

(1)创建套接字(socket)

(2)向服务器发出连接请求(connect)

(3)和服务器端进行通信(send/recv)

(4)关闭套接字(close)

服务器端的流程如下:

(1)创建套接字(socket)

(2)将套接字绑定到一个本地地址和端口上(bind)

(3)将套接字设为监听模式,准备接收客户端请求(listen)

(4)等待客户请求到来;当请求到来后,接受连接请求,返回一个新的对应于此次连接的套接字(accept)

(5)用返回的套接字和客户端进行通信(send/recv)

(6)循环或关闭套接字。(close)

函数情况:

(一)创建套接字socket连接:

#include <sys/types.h> #include <sys/socket.h>int socket(int domain, int type, int protocol);

参数:
domain指明了协议簇:
Name Purpose Man pageAF_UNIX, AF_LOCAL Local communication unix(7)AF_INET IPv4 Internet protocols ip(7)AF_INET6 IPv6 Internet protocols ipv6(7)AF_IPX IPX - Novell protocolsAF_NETLINK Kernel user interface device netlink(7)AF_X25 ITU-T X.25 / ISO-8208 protocol x25(7)AF_AX25 Amateur radio AX.25 protocolAF_ATMPVC Access to raw ATM PVCsAF_APPLETALK AppleTalk ddp(7)AF_PACKET Low level packet interface packet(7)AF_ALG Interface to kernel crypto API

其中AF_INET代表ip4


type套接字类型:
SOCK_STREAM Provides sequenced, reliable, two-way, connection-basedbyte streams. An out-of-band data transmission mechanism may be supported.SOCK_DGRAM Supports datagrams (connectionless, unreliable messages of a fixed maximum length).SOCK_SEQPACKET Provides a sequenced, reliable, two-way connectionbased data transmission path for datagrams of fixed maximum length; a consumer is required to read an entire packet with each input system call.SOCK_RAW Provides raw network protocol access.SOCK_RDM Provides a reliable datagram layer that does not guarantee ordering.SOCK_PACKET Obsolete and should not be used in new programs; see packet(7).
其中SOCK_STREAM代表有序、可靠、双向的面向连接字节流(即tcp)
SOCK_DGRAM代表长度固定的、无连接的不可靠的保温传递(即udp)
protocol当存在多个协议时,默认使用的协议。通常取0。

(二)绑定地址:bind

#include <sys/types.h> #include <sys/socket.h>int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数:
sockfd:socket返回的套接字id

addr:保存绑定的地址:

struct sockaddr_in { sa_family_t sa_family; //协议族[1] char sa_family[14];        //地址[14]};


这是通用的地址结构体;

下面的时tcp专用的地址结构体,两者的长度一致,形式不同:
struct sockaddr_in {sa_family_t sin_family;//协议族[1]in_port_t sin_port;//端口号[2]struct in_addr sin_addr;//ip地址[4]char sin_zero[8];//填充字节[8],共[15]};
其中ip地址结构体struct in_addr的定义为:
struct in_addr {in_addr_t s_addr;     }; 
len:地址的长度:
sizeof(struct sockaddr_in);


(三)ip地址的数据类型

由于ip是由4位16进制数组成,“10.0.2.15”时字符串型,这里存在字符串与整形的变换:
字符串转化为整形:
in_addr_t inet_addr(const char *cp);
参数是char型,返回值是struct in_addr中的成员:in_addr_t型的s_addr;
整形转化为字符串型:
char *inet_ntoa(struct in_addr in);
参数是结构体struct in_addr,可见上下两个函数并不对等;
注意,上面两种函数涉及的数据形式都是网络字节序,大端!


(四)网络字节序

由于网络数据传输存在先后顺序,所以产生字节序问题
网络字节序都是大端
主机字节序不定,有大端也有小端
头文件:
#include <arpa/inet.h>

发送方:由主机->网络字节序
uint32_t htonl(uint32_t hostlong);uint16_t htons(uint16_t hostshort);

发送方:由网络->主机字节序
uint32_t ntohl(uint32_t netlong);uint16_t ntohs(uint16_t netshort);

其中:in_addr.s_addr=htonl(INADDR_ANY)指代服务器可接受主机一切网络设备(ip)上传来的请求


(五)监听设备:listen

#include <sys/types.h> #include <sys/socket.h>int listen(int sockfd, int backlog);

参数:
sockfd:socket返回的套接字id;
backlog:接收客户机的数量

返回值:success:0
error:-1


(六)等待连接:accept

#include <sys/types.h> #include <sys/socket.h>int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

参数:
sockfd:socket返回的套接字id;
addr:保存绑定的地址:
addrlen:addr长度的指针,注意,这里是指针!必须取地址赋值!

返回值:success:新的套接字id
error:-1


(七)发送数据:send

#include <sys/types.h>#include <sys/socket.h>ssize_t send(int sockfd, const void *buf, size_t len, int flags);

参数:
sockfd:socket返回的套接字id;
buf:数据存放的地址,指针
len:数据长度
flags:发送选项,通常取0

返回值:success:新的套接字id
error:-1


(八)接收数据:recv

#include <sys/types.h>#include <sys/socket.h>ssize_t recv(int sockfd, void *buf, size_t len, int flags);

参数:
sockfd:socket返回的套接字id;
buf:数据存放的地址,指针
len:数据长度
flags:发送选项,通常取0

返回值:success:新的套接字id
error:-1


(九)客户端连接:connect

#include <sys/types.h> #include <sys/socket.h>int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

sockfd:socket返回的套接字id;
addr:保存绑定的地址:
addrlen:addr长度的指针,注意,这里是不是指针!跟accept不一样!

上代码:

tcp_server.c:

#include <stdio.h>#include <sys/socket.h>#include <string.h>#include <netinet/in.h>#define portnum 2333void main(){int sockfd, new_fd;struct sockaddr_in server_addr;struct sockaddr_in client_addr;char buffer[128];int nbyte;int size_addr;//1、创建套接字if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1){printf("sockfd create error!\n");exit(1);}//2.1、设置绑定地址bzero(&server_addr, sizeof(struct sockaddr_in));//清零server_addr结构server_addr.sin_family = AF_INET;server_addr.sin_port = htons(portnum);//转换套接字server_addr.sin_addr.s_addr = htonl(INADDR_ANY);//2.2、绑定地址bind(sockfd, (struct sockaddr *) (&server_addr), sizeof(struct sockaddr));//3、监听端口listen(sockfd, 5);while (1){//4、等待连接,忽略对new_fd的判断验证size_addr=sizeof(struct sockaddr);new_fd = accept(sockfd, (struct sockaddr *) (&client_addr), &size_addr);printf("server get connection from %s,%d\n", inet_ntoa(client_addr.sin_addr),client_addr.sin_addr.s_addr);//5、接受数据nbyte=recv(new_fd, buffer, 128, 0);buffer[nbyte] = '\0';printf("server received is %s\n", buffer);//6、结束连接close(new_fd);}close(sockfd);return 1;}
tcp_client.c:

#include <stdio.h>#include <sys/socket.h>#include <string.h>#include <netinet/in.h>#define portnum 2333int main(){int sockfd;struct sockaddr_in server_addr;char buffer[128];//1、创建套接字if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1){printf("sockfd create error!\n");exit(1);}//2.1、设置绑定地址bzero(&server_addr, sizeof(struct sockaddr_in));//清零server_addr结构server_addr.sin_family = AF_INET;server_addr.sin_port = htons(portnum);//转换套接字server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");printf("the distance is %s",inet_ntoa(server_addr.sin_addr));//2.2、连接到服务器if (connect(sockfd, (struct sockaddr *) (&server_addr), sizeof(struct sockaddr)) == -1){printf("connect error!\n");exit(1);}//3、发送数据到服务器printf("please enter char:\n");fgets(buffer, 128,stdin);send(sockfd, buffer, strlen(buffer), 0);//4、关闭连接close(sockfd);return 1;}
期间遇到很多莫名其妙的问题,客户端一致无法连接,服务器又不停滴接收到来自0.0.0.0的通讯,后者莫名其妙地解决了,至今不知道是什么原因导致的,前者gdb调试+百度谷歌仍然找不出原因,随后从网上找来被人的代码对比试验:

http://blog.csdn.net/chocolate001/article/details/6612201
经过两天前前后后的探寻才终于发现原因:

((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
问题出在这句话上,一开始粗心,写成了:

(sockfd = socket(AF_INET, SOCK_STREAM, 0) == -1)
因为==的优先级高于=,所以结果肯定是出错了,因而倒是无法识别正确的id(或者说fd)更准确些。


接下来优化服务器代码:

通过把第五第六步(数据处理和结束连接)放到子进程中执行,实现服务器对多个客户端同时访问的请求。

#include <stdio.h>#include <sys/socket.h>#include <string.h>#include <netinet/in.h>#define portnum 2333void main(){int sockfd, new_fd;struct sockaddr_in server_addr;struct sockaddr_in client_addr;char buffer[128];int nbyte,pid;int size_addr;//1、创建套接字if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1){printf("sockfd create error!\n");exit(1);}//2.1、设置绑定地址bzero(&server_addr, sizeof(struct sockaddr_in));//清零server_addr结构server_addr.sin_family = AF_INET;server_addr.sin_port = htons(portnum);//转换套接字server_addr.sin_addr.s_addr = htonl(INADDR_ANY);//server_addr.sin_addr.s_addr = inet_addr("10.0.2.15");//2.2、绑定地址bind(sockfd, (struct sockaddr *) (&server_addr), sizeof(struct sockaddr));//3、监听端口listen(sockfd, 5);while (1){//4、等待连接,忽略对new_fd的判断验证size_addr=sizeof(struct sockaddr);new_fd = accept(sockfd, (struct sockaddr *) (&client_addr), &size_addr);printf("server get connection from %s,%d\n", inet_ntoa(client_addr.sin_addr),client_addr.sin_addr.s_addr);//create child fork immdiatelyif(pid=fork()==0){//5、接受数据nbyte=recv(new_fd, buffer, 128, 0);buffer[nbyte] = '\0';printf("server received is %s\n", buffer);close(new_fd);close(sockfd);exit(0);}else if(pid<0)printf("fork error!\n");//6、结束连接close(new_fd);}close(sockfd);return 1;}


阅读全文
0 0
原创粉丝点击