前言
socket是在TCP/IP协议上一个种封装,即调用tcp/ip的接口。 socket:可设置为,TCP(流)与UDP(数据报)
UDP:使用无连接协议就像寄信 TCP:使用面向连接的协议就像打电话
socket系统调用过程(TCP):
服务端:
1、socket()创建一个socket
2、bind()将一个socket绑定到一个地址
3、listen()允许流socket接受其他socket接入连接
4、accept()监听流socket上请求的连接
5、read()或recv() / wirte()或send() 进行信息交互
6、close()关闭socket
客户端:
1、socket()创建一个socket
2、connect()与服务端进行连接
3、 wirte()或send() / read()或recv() 进行信息交互
4、close()关闭socket
一、使用的API
头文件: #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h>
1、socket()创建
int socket(int domain, int type, int protocol);
success:返回文件描述符 error: -1 错误代码存入errno中
domain: 一般把它赋为AF_INET
AF_INET:使用IPv4 TCP/IP协议
AF_INET6:使用IPv6 TCP/IP协议
AF_UNIX:创建只在本机内进行通信的套接字
type:
SOCK_STREAM:创建TCP流套接字
SOCK_DGRAM:创建UDP数据报套接字
SOCK_RAW:创建原始套接字
protocol:通常设置为0
2、bind()绑定地址
①、bind()
int bind(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
success:0 error: -1 错误代码存入errno中
sockfd:socket()返回的描述符
addr:指向sockaddr 的的指针,保存着本地套接字的地址(即端口和IP地址)
信息。不过由于系统兼容性的问题,一般不使用这个结构,而使用另外一个
结构(struct sockaddr_in)来代替
addrlen:sockaddr结构的长度
对于IPV4使用以下结构体:
struct sockaddr_in{
unsigned short sin_family;
unsigned short sin_port;
struct in_addr sin_addr;
unsigned char sin_zero[8];
};
struct in_addr {
uint32_t s_addr;
};
eg: (struct sockaddr_in srv_addr;)
bzero(&srv_addr,sizeof(struct sockaddr_in));
srv_addr.sin_family = AF_INET;
srv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
srv_addr.sin_port = htons(8000);
INADDR_ANY:让服务器端计算机上的所有网卡的IP地址都可以作为服务器IP地址,也即监听外部客户端程序发送到服务器端所有网卡的网络请求。
如果需要使用特定的IP地址则需要使用inet_addr 和inet_ntoa完成字符串和in_addr结构体的互换
②、网络字节序转换:
整型与整型转换:
uint32_t htonl(uint32_t hostlong);
将一个32位数从主机字节顺序转换成网络字节顺序
uint16_t htons(uint16_t hostshort);
将一个无符号短整型数值转换为网络字节序
uint32_t ntohl(uint32_t netlong);
将网络字节序转为一个32位数主机字节用于打印显示
uint16_t ntohs(uint16_t netshort);
将网络字节序转为一个16位数主机字节用于打印显示
字符与整型转换:
int inet_aton(const char *cp, struct in_addr *inp);
将字符串型的ip地址(192.168.136.18)转成网络字节序的sockaddr_in.in_addr
in_addr_t inet_addr(const char *cp);
将字符串型ip转成网络字节序 对255.255.255.255 无效
in_addr_t inet_network(const char *cp);
将字符串型ip转成主机字节序
char* inet_ntoa(struct in_addr inaddr);
将网络字节序转成字符型ip 用于打印
3、listen()转为被动监听
int listen(int sockfd, int backlog);
success:0 error: -1 错误代码存入errno中
sockfd: socket返回的文件描述符
backlog:进入监听队列中允许的最大连接数目。
大多数系统的允许数目是20,我们可以设置为5到10,在还没调用accept
前,客户端connect请求成功的数量,超出则阻塞,等到调accept进行连
接
4、accept()等待请求连接
int accept(int sockfd, struct sockaddr *addr,
socklen_t *addrlen);
success:返回新的文件描述符用于收发 error: -1 错误代码存入errno中
sockfd: socket返回的文件描述符
addr:用于存储客户端的ip(ip地址、端口)
addrlen:调用前为 addr指向缓冲区大小,调用后为实际被复制缓冲区的大小
5、connect()请求连接服务端
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
success:0 error: -1 错误代码存入errno中
sockfd:客户端socket返回的描述符
addr: 想连接服务端的ip(ip地址和端口 类似bind时的addr)
addrlen: sizeof(struct sockaddr)
6、send() 发送 与 recv()接收
可阻塞等待和通过fcntl设为非阻塞
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
success: 返回实际发送的数据大小 error:-1,错误原因存于errno
sockfd: 服务端为 accept返回的描述符 客户端为socket返回的描述符
buf: 待发送的数据 和 接收的缓冲区
len: 待发送的字节数 和 接收的最大字节数(如接收超过只会收到max)
flags: 置为 0
7、close()关闭socket
int close(int fd);
success: 0 error: -1
服务端需要关闭 socket返回的 和 accept返回的
客户端 只要关闭socket返回的
二、案例
1.简单服务端和客户端连接互传信息
stdin输入客户端先发送给服务端,服务端接收后,再stdin输入发送给客户端 如此反复。
tcp_server.c:
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <pthread.h>
int main(int argc, char *argv[])
{
int skfd,cnfd,addr_len;
struct sockaddr_in srv_addr,clt_addr;
int portnumber;
char buf[128]={0};
if(2 != argc || 0 > (portnumber=atoi(argv[1])))
{
printf("Usage:%s port\n",argv[1]);
exit(1);
}
if(-1 == (skfd=socket(AF_INET,SOCK_STREAM,0)))
{
perror("Socket Error:");
exit(1);
}
bzero(&srv_addr,sizeof(struct sockaddr_in));
srv_addr.sin_family=AF_INET;
inet_aton("192.168.117.128",&srv_addr.sin_addr);
srv_addr.sin_port=htons(portnumber);
if(-1 == bind(skfd,(struct sockaddr *)(&srv_addr),sizeof(struct sockaddr)))
{
perror("Bind error:");
exit(1);
}
printf("bind----%d\r\n",skfd);
if(-1 == listen(skfd,5))
{
perror("Listen error:");
exit(1);
}
printf("listen----%d\r\n",skfd);
printf("accept\n");
addr_len=sizeof(struct sockaddr_in);
if(-1 == (cnfd=accept(skfd,(struct sockaddr *)(&clt_addr),&addr_len)))
{
perror("Accept error:");
exit(1);
}
printf("accept----%d\r\n",skfd);
printf("Connect from %s:%u ...!\n",inet_ntoa(clt_addr.sin_addr),ntohs(clt_addr.sin_port));
while(1)
{
memset(buf, 0, sizeof(buf));
if(-1 == recv(cnfd,buf,sizeof(buf),0)){
perror("recv error:");
exit(1);
}
printf("server recv from client:%s\r\n",buf);
printf("please input msg to client:");
memset(buf, 0, sizeof(buf));
fgets(buf, 4096, stdin);
if(-1 == send(cnfd,buf,strlen(buf),0)){
perror("Send error:");
exit(1);
}
if(strncmp(buf, "end",3) == 0)
break;
}
close(cnfd);
close(skfd);
exit(0);
}
tcp_client.c:
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <arpa/inet.h>
int main(int argc, char *argv[])
{
int skfd,ret;
char buf[128] = {0};
struct sockaddr_in server_addr;
int portnumber,nbytes;
if(3 != argc || 0>(portnumber=atoi(argv[2])))
{
printf("Usage:%s hostname portnumber \n", argv[0]);
exit(1);
}
if(-1 == (skfd=socket(AF_INET,SOCK_STREAM,0)))
{
perror("Socket Error:");
exit(1);
}
printf("socket----%d\r\n",skfd);
bzero(&server_addr,sizeof(server_addr));
server_addr.sin_family=AF_INET;
server_addr.sin_port=htons(portnumber);
inet_aton(argv[1],&server_addr.sin_addr);
while(1)
{
if(-1 == connect(skfd,(struct sockaddr *)(&server_addr),sizeof(struct sockaddr)))
{
perror("Connect Error:");
sleep(2);
}
else
{
printf("connect----%d\r\n",skfd);
break;
}
}
while(1)
{
memset(buf, 0, sizeof(buf));
printf("please input msg to server:");
fgets(buf, 4096, stdin);
ret = send(skfd,buf,strlen(buf),0);
printf("send:ret=%d, %ld\r\n",ret, strlen(buf));
if(-1 == ret)
{
perror("Send error:");
exit(1);
}
if(strncmp(buf, "end",3) == 0)
break;
memset(buf, 0, sizeof(buf));
if(-1 == recv(skfd,buf,sizeof(buf),0))
{
perror("recv Error:");
}
printf("client read from server:%s\r\n",buf);
if(strncmp(buf, "end",3) == 0)
break;
}
close(skfd);
exit(0);
}
2.服务端可被多个客户端连接(thread)
服务端可被多个客户端连接,当有客户端请求连接时创建一个线程去维护 tcp_server_thread.c:
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <pthread.h>
#define CLIENT_MAX 2
int cnfd[CLIENT_MAX];
int cnfd_valid[CLIENT_MAX];
void* thread_client_handle( void *arg )
{
char buf[32];
int cnfd_ix;
int ret;
cnfd_ix = *(int *)arg;
printf("thread_client_handle=%d\r\n", cnfd_ix);
while(1)
{
ret = read(cnfd[cnfd_ix],buf,sizeof(buf));
printf("server read from client:%s,%d,cnfd_ix=%d\r\n",buf,ret,cnfd_ix);
if(ret == 0)
{
cnfd_valid[cnfd_ix] = 0;
close(cnfd[cnfd_ix]);
break;
}
printf("please input msg to client:");
memset(buf, 0, sizeof(buf));
fgets(buf,sizeof(buf),stdin);
if(-1 == write(cnfd[cnfd_ix],buf,strlen(buf)+1)){
perror("Send error:");
}
}
return 0;
}
int main(int argc, char *argv[])
{
int newfd;
int skfd,addr_len;
struct sockaddr_in srv_addr,clt_addr;
int portnumber;
int ret,i=0;
pthread_attr_t attr;
pthread_t th_id0;
if(2 != argc || 0 > (portnumber=atoi(argv[1])))
{
printf("Usage:%s port\n",argv[1]);
exit(1);
}
pthread_attr_init( &attr );
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
memset(cnfd_valid, 0, sizeof(cnfd_valid));
if(-1 == (skfd=socket(AF_INET,SOCK_STREAM,0)))
{
perror("Socket Error:");
exit(1);
}
bzero(&srv_addr,sizeof(struct sockaddr_in));
srv_addr.sin_family=AF_INET;
srv_addr.sin_addr.s_addr= htonl(INADDR_ANY);
srv_addr.sin_port=htons(portnumber);
if(-1 == bind(skfd,(struct sockaddr *)(&srv_addr),sizeof(struct sockaddr)))
{
perror("Bind error:");
exit(1);
}
if(-1 == listen(skfd,10))
{
perror("Listen error:");
exit(1);
}
while(1)
{
printf("accept\n");
addr_len=sizeof(struct sockaddr_in);
if(-1 == (newfd=accept(skfd,(struct sockaddr *)(&clt_addr),&addr_len)))
{
perror("Accept error:");
exit(1);
}
printf("Connect from %s:%u ...\n",inet_ntoa(clt_addr.sin_addr),ntohs(clt_addr.sin_port));
for(i=0;i<CLIENT_MAX;i++)
{
if(cnfd_valid[i] == 0)
{
cnfd_valid[i] = 1;
cnfd[i] = newfd;
break;
}
}
if(i>=CLIENT_MAX)
continue;
pthread_create( &th_id0, &attr, thread_client_handle, &i);
}
for(i=0;i<CLIENT_MAX; i++)
{
if(cnfd_valid[i] == 1)
{
close(cnfd[i]);
}
}
close(skfd);
exit(0);
}
tcp_client_thread.c:
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <arpa/inet.h>
int main(int argc, char *argv[])
{
int skfd;
char buf[128] = {0};
struct sockaddr_in server_addr;
int portnumber,nbytes;
if(3 != argc || 0>(portnumber=atoi(argv[2])))
{
printf("Usage:%s hostname portnumber \n", argv[0]);
exit(1);
}
if(-1 == (skfd=socket(AF_INET,SOCK_STREAM,0)))
{
perror("Socket Error:");
exit(1);
}
bzero(&server_addr,sizeof(server_addr));
server_addr.sin_family=AF_INET;
server_addr.sin_port=htons(portnumber);
inet_aton(argv[1],&server_addr.sin_addr);
if(-1 == connect(skfd,(struct sockaddr *)(&server_addr),sizeof(struct sockaddr)))
{
perror("Connect Error:");
exit(1);
}
while(1)
{
memset(buf, 0, sizeof(buf));
printf("please input msg to server:");
scanf("%s",buf);
if(-1 == write(skfd,buf,strlen(buf)+1)){
perror("Send error:");
exit(1);
}
if(strcmp(buf, "end") == 0)
break;
memset(buf, 0, sizeof(buf));
if(-1 == read(skfd,buf,sizeof(buf)))
{
perror("read Error:");
}
printf("client read from server:%s\r\n",buf);
if(strncmp(buf, "end",3) == 0)
break;
}
close(skfd);
exit(0);
}
3、服务器可被多个客户端连接(I/O多路复用)
待更新。
进程间的通讯:SOCKET(UDP)
|