socket编程——一个简单的例子
从一个简单的使用TCP例子开始socket编程,其基本步骤如下:
server client
+++++++ ++++++++
创建socket 创建socket
+++++++ ++++++++
| |
| |
| |
+++++++ ++++++++
地址赋值( 地址赋值(
自己的地址) 服务器地址)
+++++++ ++++++++
| |
| |
| |
++++++++ |
用bind绑定 |
socket和地址 |
++++++++ |
| |
| |
| |
+++++++ |
listen |
+++++++ |
| ++++++++++
| <------------------------------ connect 服务器
| ++++++++++
+++++++ |
accept |
+++++++ |
| |
| +++++++++
| recv 和send
| 进行数据处理
| +++++++++
+++++++++ |
用accept得到 |
的socket进行 |
recv 和 send |
+++++++++ |
| |
| |
| |
+++++++++ +++++++++
close socket close socket
+++++++++ +++++++++
根据以上步骤,服务器端的代码为
#include <stdio.h> #include <string.h> #include <sys/socket.h> #include <netinet/in.h> #include <stdlib.h> #include <syslog.h> #include <errno.h> #define MAX_LISTEN_NUM 5 #define SEND_BUF_SIZE 100 #define RECV_BUF_SIZE 100 #define LISTEN_PORT 1010 int main() { int listen_sock = 0; int app_sock = 0; struct sockaddr_in hostaddr; struct sockaddr_in clientaddr; int socklen = sizeof(clientaddr); char sendbuf[SEND_BUF_SIZE] = {0}; char recvbuf[RECV_BUF_SIZE] = {0}; int sendlen = 0; int recvlen = 0; int retlen = 0; int leftlen = 0; char *ptr = NULL; memset((void *)&hostaddr, 0, sizeof(hostaddr)); memset((void *)&clientaddr, 0, sizeof(clientaddr)); hostaddr.sin_family = AF_INET; hostaddr.sin_port = htons(LISTEN_PORT); hostaddr.sin_addr.s_addr = htonl(INADDR_ANY); listen_sock = socket(AF_INET, SOCK_STREAM, 0); if(listen_sock < 0) { syslog(LOG_ERR, "%s:%d, create socket failed", __FILE__, __LINE__); exit(1); } if(bind(listen_sock, (struct sockaddr *)&hostaddr, sizeof(hostaddr)) < 0) { syslog(LOG_ERR, "%s:%d, bind socket failed", __FILE__, __LINE__); exit(1); } if(listen(listen_sock, MAX_LISTEN_NUM) < 0) { syslog(LOG_ERR, "%s:%d, listen failed", __FILE__, __LINE__); exit(1); } while(1) { app_sock = accept(listen_sock, (struct sockaddr *)&clientaddr, &socklen); if(app_sock < 0) { syslog(LOG_ERR, "%s:%d, accept failed", __FILE__, __LINE__); exit(1); } sprintf(sendbuf, "welcome %s:%d here!/n", inet_ntoa(clientaddr.sin_addr.s_addr), clientaddr.sin_port); //send data sendlen = strlen(sendbuf) +1; retlen = 0; leftlen = sendlen; ptr = sendbuf; //while(leftlen) { retlen = send(app_sock, ptr, sendlen, 0); if(retlen < 0) { if(errno == EINTR) retlen = 0; else exit(1); } leftlen -= retlen; ptr += retlen; } //receive data recvlen = 0; retlen = 0; ptr = recvbuf; leftlen = RECV_BUF_SIZE -1; //do { retlen = recv(app_sock, ptr, leftlen, 0) ; if(retlen < 0) { if(errno == EINTR) retlen = 0; else exit(1); } recvlen += retlen; leftlen -= retlen; ptr += retlen; } //while(recvlen && leftlen); printf("receive data is : %s", recvbuf); close(app_sock); } close(listen_sock); return 0; }
客户端代码为:
#include <stdio.h> #include <string.h> #include <sys/socket.h> #include <netinet/in.h> #include <syslog.h> #include <errno.h> #include <stdlib.h> #define MAX_LISTEN_NUM 5 #define SEND_BUF_SIZE 100 #define RECV_BUF_SIZE 100 #define SERVER_PORT 1010 int main() { int sock_fd = 0; char recvbuf[RECV_BUF_SIZE] = {0}; char sendbuf[SEND_BUF_SIZE] = {0}; int recvlen = 0; int retlen = 0; int sendlen = 0; int leftlen = 0; char *ptr = NULL; struct sockaddr_in ser_addr; memset(&ser_addr, 0, sizeof(ser_addr)); ser_addr.sin_family = AF_INET; inet_aton("127.0.0.1", (struct in_addr *)&ser_addr.sin_addr); ser_addr.sin_port = htons(SERVER_PORT); sock_fd = socket(AF_INET, SOCK_STREAM, 0); if(sock_fd < 0) { syslog(LOG_ERR, "%s:%d, create socket failed", __FILE__, __LINE__); exit(1); } if(connect(sock_fd, (struct sockaddr *)&ser_addr, sizeof(ser_addr)) < 0) { syslog(LOG_ERR, "%s:%d, connect socket failed", __FILE__, __LINE__); exit(1); } //receive data recvlen = 0; retlen = 0; ptr = recvbuf; leftlen = RECV_BUF_SIZE -1; //do { retlen = recv(sock_fd, ptr, leftlen, 0) ; if(retlen < 0) { if(errno == EINTR) retlen = 0; else exit(1); } recvlen += retlen; leftlen -= retlen; ptr += retlen; } //while(recvlen && leftlen); printf("receive data is : %s", recvbuf); sprintf(sendbuf, "hello server/n"); //send data sendlen = strlen(sendbuf) +1; retlen = 0; leftlen = sendlen; ptr = sendbuf; // while(leftlen) { retlen = send(sock_fd, ptr, sendlen, 0); if(retlen < 0) { if(errno == EINTR) retlen = 0; else exit(1); } leftlen -= retlen; ptr += retlen; } close(sock_fd); }
现在一个简单的使用tcp的socket通信的例子已经完成了,这里有几个需要说明的问题
1)头文件:
sys/socket.h 包含了socket相关的函数,如socket,send 和recv, 以及struct sockaddr等
netinet/in.h 包含了地址结构,如struct sockaddr_in
errno.h 包含了errno 和 EINTR
syslog.h 包含了syslog相关的信息,其打印结果在/var/log/messages里面
2)socket地址
对于IPv4来说,其地址用的是struct sockaddr_in,具体结构如下
struct in_addr { in_addr_t s_addr; /* 32-bit IPv4 address */ /* network byte ordered */ }; struct sockaddr_in { uint8_t sin_len; /* length of structure (16) */ sa_family_t sin_family; /* AF_INET */ in_port_t sin_port; /* 16-bit TCP or UDP port number */ /* network byte ordered */ struct in_addr sin_addr; /* 32-bit IPv4 address */ /* network byte ordered */ char sin_zero[8]; /* unused */ };
其中sin_len我们一般不关注,也不填(只有在使用routing socket的时候才用到,被内核用来处理各种协议簇的地址结构)。 bind, connect, sendto, 和 sendmsg会把socket地址从程序传递给内核; 而accept, recvfrom, recvmsg, getpeername, 和
getsockname会把地址从内核传递给程序。因为不同协议簇的地址结构是不一样的,所以必须要有一个通用的指针来传递地址,
对于ANSI C来说我们一般使用void *,但是socket产生早于ANSI C,所以也就没有使用这个机制,而是使用一个通用的地址结构
struct sockaddr来处理的
struct sockaddr { uint8_t sa_len; sa_family_t sa_family; /* address family: AF_xxx value */ char sa_data[14]; /* protocol-specific address */ };
IPv6的socket地址为struct sockaddr_in6
struct in6_addr { uint8_t s6_addr[16]; /* 128-bit IPv6 address */ /* network byte ordered */ }; #define SIN6_LEN /* required for compile-time tests */ struct sockaddr_in6 { uint8_t sin6_len; /* length of this struct (28) */ sa_family_t sin6_family; /* AF_INET6 */ in_port_t sin6_port; /* transport layer port# */ /* network byte ordered */ uint32_t sin6_flowinfo; /* flow information, undefined */ struct in6_addr sin6_addr; /* IPv6 address */ /* network byte ordered */ uint32_t sin6_scope_id; /* set of interfaces for a scope */ };
对于sockaddr-in6来说,我们不能用通用的地址struct sockaddr来存储了,而是产用新的通用地址结构struct sockaddr_storage,
这个结构足够大可以存储任何系统支持的地址。
struct sockaddr_storage { uint8_t ss_len; /* length of this struct (implementation dependent) */ sa_family_t ss_family; /* address family: AF_xxx value */ /* implementation-dependent elements to provide: * a) alignment sufficient to fulfill the alignment requirements of * all socket address types that the system support * b) enough storage to hold any type of socket address that the * system supports. */ };
几种常见的地址结构
3) 相关函数的的length 对于从程序传地址给内核的函数(如connect),其长度是一个整型值,告诉内核要copy的地址长度。 对于从内核传递给程序的函数(如accpt),其长度是一个整型指针,是一个value-result参数。有两个目的:一告诉内核地址结构的长度,
让内核在copy的时候不要超过这个长度;二返回内核真正copy的长度。 4)字节序 socket相关的函数都是使用网络字节序 5)地址转换函数 inet_aton, inet_ntoa, and inet_addr把IPv4字符串地址转为32位的网络字节序地址 inet_ptonand inet_ntop可以转换IPv4和IPv6的地址 6)listen中的backlog 要知道这个值的含义先用说一下,对于一个listen的socket,有两个队列:一个是incomplete connection队列(仅仅收到SYN);
一个是complete connection队列(三次握手完成)。accept函数就是在complete connection队列中取一个socket。backlog就是指队列的个数,
但不行的是各个地方都没有明确定义这个值,没有说明究竟代表了哪个队列,或是两个队列之和。一般来说可以 同时处理的连接数是backlog的1.5倍,很多地方都用5. 7) getsockname 和 getpeername 这两个函数可以与socket关联的地址,getsockname 和 getpeername分别得到自己和对端的地址
int getsockname(int sockfd, struct sockaddr *localaddr, socklen_t *addrlen) int getpeername(int sockfd , struct sockaddr * peeraddr , socklen_t * addrlen );
FROM: http://blog.csdn.net/wind19/article/details/6156339